diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc
index 6b30e3a9e..0d729275b 100644
--- a/common/src/app/common/pages/helpers.cljc
+++ b/common/src/app/common/pages/helpers.cljc
@@ -161,6 +161,12 @@
(when parent-id
(lazy-seq (cons parent-id (get-parents parent-id objects))))))
+(defn get-frame
+ "Get the frame that contains the shape. If the shape is already a frame, get itself."
+ [shape objects]
+ (if (= (:type shape) :frame)
+ shape
+ (get objects (:frame-id shape))))
(defn clean-loops
"Clean a list of ids from circular references."
diff --git a/common/src/app/common/pages/spec.cljc b/common/src/app/common/pages/spec.cljc
index da4f79ae3..a1db691f2 100644
--- a/common/src/app/common/pages/spec.cljc
+++ b/common/src/app/common/pages/spec.cljc
@@ -9,6 +9,7 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.spec :as us]
+ [app.common.types.interactions :as cti]
[app.common.uuid :as uuid]
[clojure.set :as set]
[clojure.spec.alpha :as s]))
@@ -30,9 +31,6 @@
(s/def ::component-root? boolean?)
(s/def ::shape-ref uuid?)
-(s/def ::safe-integer ::us/safe-integer)
-(s/def ::safe-number ::us/safe-number)
-
(s/def :internal.matrix/a ::us/safe-number)
(s/def :internal.matrix/b ::us/safe-number)
(s/def :internal.matrix/c ::us/safe-number)
@@ -61,15 +59,15 @@
;; GRADIENTS
(s/def :internal.gradient.stop/color ::string)
-(s/def :internal.gradient.stop/opacity ::safe-number)
-(s/def :internal.gradient.stop/offset ::safe-number)
+(s/def :internal.gradient.stop/opacity ::us/safe-number)
+(s/def :internal.gradient.stop/offset ::us/safe-number)
(s/def :internal.gradient/type #{:linear :radial})
-(s/def :internal.gradient/start-x ::safe-number)
-(s/def :internal.gradient/start-y ::safe-number)
-(s/def :internal.gradient/end-x ::safe-number)
-(s/def :internal.gradient/end-y ::safe-number)
-(s/def :internal.gradient/width ::safe-number)
+(s/def :internal.gradient/start-x ::us/safe-number)
+(s/def :internal.gradient/start-y ::us/safe-number)
+(s/def :internal.gradient/end-x ::us/safe-number)
+(s/def :internal.gradient/end-y ::us/safe-number)
+(s/def :internal.gradient/width ::us/safe-number)
(s/def :internal.gradient/stop
(s/keys :req-un [:internal.gradient.stop/color
@@ -95,7 +93,7 @@
(s/def :internal.color/path (s/nilable ::string))
(s/def :internal.color/value (s/nilable ::string))
(s/def :internal.color/color (s/nilable ::string))
-(s/def :internal.color/opacity (s/nilable ::safe-number))
+(s/def :internal.color/opacity (s/nilable ::us/safe-number))
(s/def :internal.color/gradient (s/nilable ::gradient))
(s/def ::color
@@ -113,10 +111,10 @@
(s/def :internal.shadow/id uuid?)
(s/def :internal.shadow/style #{:drop-shadow :inner-shadow})
(s/def :internal.shadow/color ::color)
-(s/def :internal.shadow/offset-x ::safe-number)
-(s/def :internal.shadow/offset-y ::safe-number)
-(s/def :internal.shadow/blur ::safe-number)
-(s/def :internal.shadow/spread ::safe-number)
+(s/def :internal.shadow/offset-x ::us/safe-number)
+(s/def :internal.shadow/offset-y ::us/safe-number)
+(s/def :internal.shadow/blur ::us/safe-number)
+(s/def :internal.shadow/spread ::us/safe-number)
(s/def :internal.shadow/hidden boolean?)
(s/def :internal.shadow/shadow
@@ -137,7 +135,7 @@
(s/def :internal.blur/id uuid?)
(s/def :internal.blur/type #{:layer-blur})
-(s/def :internal.blur/value ::safe-number)
+(s/def :internal.blur/value ::us/safe-number)
(s/def :internal.blur/hidden boolean?)
(s/def ::blur
@@ -148,17 +146,17 @@
;; Page Options
(s/def :internal.page.grid.color/value string?)
-(s/def :internal.page.grid.color/opacity ::safe-number)
+(s/def :internal.page.grid.color/opacity ::us/safe-number)
-(s/def :internal.page.grid/size ::safe-integer)
+(s/def :internal.page.grid/size ::us/safe-integer)
(s/def :internal.page.grid/color
(s/keys :req-un [:internal.page.grid.color/value
:internal.page.grid.color/opacity]))
(s/def :internal.page.grid/type #{:stretch :left :center :right})
-(s/def :internal.page.grid/item-length (s/nilable ::safe-integer))
-(s/def :internal.page.grid/gutter (s/nilable ::safe-integer))
-(s/def :internal.page.grid/margin (s/nilable ::safe-integer))
+(s/def :internal.page.grid/item-length (s/nilable ::us/safe-integer))
+(s/def :internal.page.grid/gutter (s/nilable ::us/safe-integer))
+(s/def :internal.page.grid/margin (s/nilable ::us/safe-integer))
(s/def :internal.page.grid/square
(s/keys :req-un [:internal.page.grid/size
@@ -183,25 +181,6 @@
(s/def :internal.page/options
(s/keys :opt-un [:internal.page.options/background]))
-;; Interactions
-
-(s/def :internal.shape.interaction/event-type #{:click :hover})
-(s/def :internal.shape.interaction/action-type #{:navigate :open-overlay :close-overlay})
-(s/def :internal.shape.interaction/destination (s/nilable ::uuid))
-
-(s/def :internal.shape/interaction
- (s/keys :req-un [:internal.shape.interaction/event-type
- :internal.shape.interaction/action-type
- :internal.shape.interaction/destination]))
-
-(s/def :internal.shape/interactions
- (s/coll-of :internal.shape/interaction :kind vector?))
-
-(def default-interaction
- {:event-type :click
- :action-type :navigate
- :destination nil})
-
;; Size constraints
(s/def :internal.shape/constraints-h #{:left :right :leftright :center :scale})
@@ -232,33 +211,33 @@
(s/def :internal.shape/content any?)
(s/def :internal.shape/fill-color string?)
-(s/def :internal.shape/fill-opacity ::safe-number)
+(s/def :internal.shape/fill-opacity ::us/safe-number)
(s/def :internal.shape/fill-color-gradient (s/nilable ::gradient))
(s/def :internal.shape/fill-color-ref-file (s/nilable uuid?))
(s/def :internal.shape/fill-color-ref-id (s/nilable uuid?))
(s/def :internal.shape/font-family string?)
-(s/def :internal.shape/font-size ::safe-integer)
+(s/def :internal.shape/font-size ::us/safe-integer)
(s/def :internal.shape/font-style string?)
(s/def :internal.shape/font-weight string?)
(s/def :internal.shape/hidden boolean?)
-(s/def :internal.shape/letter-spacing ::safe-number)
-(s/def :internal.shape/line-height ::safe-number)
+(s/def :internal.shape/letter-spacing ::us/safe-number)
+(s/def :internal.shape/line-height ::us/safe-number)
(s/def :internal.shape/locked boolean?)
(s/def :internal.shape/page-id uuid?)
-(s/def :internal.shape/proportion ::safe-number)
+(s/def :internal.shape/proportion ::us/safe-number)
(s/def :internal.shape/proportion-lock boolean?)
-(s/def :internal.shape/rx ::safe-number)
-(s/def :internal.shape/ry ::safe-number)
-(s/def :internal.shape/r1 ::safe-number)
-(s/def :internal.shape/r2 ::safe-number)
-(s/def :internal.shape/r3 ::safe-number)
-(s/def :internal.shape/r4 ::safe-number)
+(s/def :internal.shape/rx ::us/safe-number)
+(s/def :internal.shape/ry ::us/safe-number)
+(s/def :internal.shape/r1 ::us/safe-number)
+(s/def :internal.shape/r2 ::us/safe-number)
+(s/def :internal.shape/r3 ::us/safe-number)
+(s/def :internal.shape/r4 ::us/safe-number)
(s/def :internal.shape/stroke-color string?)
(s/def :internal.shape/stroke-color-gradient (s/nilable ::gradient))
(s/def :internal.shape/stroke-color-ref-file (s/nilable uuid?))
(s/def :internal.shape/stroke-color-ref-id (s/nilable uuid?))
-(s/def :internal.shape/stroke-opacity ::safe-number)
+(s/def :internal.shape/stroke-opacity ::us/safe-number)
(s/def :internal.shape/stroke-style #{:solid :dotted :dashed :mixed :none :svg})
(def stroke-caps-line #{:round :square})
@@ -271,26 +250,26 @@
[shape]
(= (:type shape) :path))
-(s/def :internal.shape/stroke-width ::safe-number)
+(s/def :internal.shape/stroke-width ::us/safe-number)
(s/def :internal.shape/stroke-alignment #{:center :inner :outer})
(s/def :internal.shape/text-align #{"left" "right" "center" "justify"})
-(s/def :internal.shape/x ::safe-number)
-(s/def :internal.shape/y ::safe-number)
-(s/def :internal.shape/cx ::safe-number)
-(s/def :internal.shape/cy ::safe-number)
-(s/def :internal.shape/width ::safe-number)
-(s/def :internal.shape/height ::safe-number)
+(s/def :internal.shape/x ::us/safe-number)
+(s/def :internal.shape/y ::us/safe-number)
+(s/def :internal.shape/cx ::us/safe-number)
+(s/def :internal.shape/cy ::us/safe-number)
+(s/def :internal.shape/width ::us/safe-number)
+(s/def :internal.shape/height ::us/safe-number)
(s/def :internal.shape/index integer?)
(s/def :internal.shape/shadow ::shadow)
(s/def :internal.shape/blur ::blur)
-(s/def :internal.shape/x1 ::safe-number)
-(s/def :internal.shape/y1 ::safe-number)
-(s/def :internal.shape/x2 ::safe-number)
-(s/def :internal.shape/y2 ::safe-number)
+(s/def :internal.shape/x1 ::us/safe-number)
+(s/def :internal.shape/y1 ::us/safe-number)
+(s/def :internal.shape/x2 ::us/safe-number)
+(s/def :internal.shape/y2 ::us/safe-number)
(s/def :internal.shape.export/suffix string?)
-(s/def :internal.shape.export/scale ::safe-number)
+(s/def :internal.shape.export/scale ::us/safe-number)
(s/def :internal.shape/export
(s/keys :req-un [::type
:internal.shape.export/suffix
@@ -366,7 +345,7 @@
:internal.shape/transform-inverse
:internal.shape/width
:internal.shape/height
- :internal.shape/interactions
+ ::cti/interactions
:internal.shape/masked-group?
:internal.shape/shadow
:internal.shape/blur]))
@@ -402,8 +381,8 @@
:internal.color/gradient]))
(s/def :internal.media-object/name ::string)
-(s/def :internal.media-object/width ::safe-integer)
-(s/def :internal.media-object/height ::safe-integer)
+(s/def :internal.media-object/width ::us/safe-integer)
+(s/def :internal.media-object/height ::us/safe-integer)
(s/def :internal.media-object/mtype ::string)
(s/def ::media-object
diff --git a/common/src/app/common/types/interactions.cljc b/common/src/app/common/types/interactions.cljc
new file mode 100644
index 000000000..f506ea7ea
--- /dev/null
+++ b/common/src/app/common/types/interactions.cljc
@@ -0,0 +1,283 @@
+;; 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/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.common.types.interactions
+ (:require
+ [app.common.geom.point :as gpt]
+ [app.common.spec :as us]
+ [clojure.spec.alpha :as s]))
+
+(s/def ::point
+ (s/and (s/keys :req-un [::x ::y])
+ gpt/point?))
+
+;; -- Options depending on event type
+
+(s/def ::event-type #{:click
+ :mouse-over
+ :mouse-press
+ :mouse-enter
+ :mouse-leave
+ :after-delay})
+
+(s/def ::delay ::us/safe-integer)
+
+(defmulti event-opts-spec :event-type)
+
+(defmethod event-opts-spec :after-delay [_]
+ (s/keys :req-un [::delay]))
+
+(defmethod event-opts-spec :default [_]
+ (s/keys :req-un []))
+
+(s/def ::event-opts
+ (s/multi-spec event-opts-spec ::event-type))
+
+;; -- Options depending on action type
+
+(s/def ::action-type #{:navigate
+ :open-overlay
+ :close-overlay
+ :prev-screen
+ :open-url})
+
+(s/def ::destination (s/nilable ::us/uuid))
+(s/def ::overlay-pos-type #{:manual
+ :center
+ :top-left
+ :top-right
+ :top-center
+ :bottom-left
+ :bottom-right
+ :bottom-center})
+(s/def ::overlay-position ::point)
+(s/def ::url ::us/string)
+(s/def ::close-click-outside ::us/boolean)
+(s/def ::background-overlay ::us/boolean)
+
+(defmulti action-opts-spec :action-type)
+
+(defmethod action-opts-spec :navigate [_]
+ (s/keys :req-un [::destination]))
+
+(defmethod action-opts-spec :open-overlay [_]
+ (s/keys :req-un [::destination
+ ::overlay-position
+ ::overlay-pos-type]
+ :opt-un [::close-click-outside
+ ::background-overlay]))
+
+(defmethod action-opts-spec :close-overlay [_]
+ (s/keys :req-un [::destination]))
+
+(defmethod action-opts-spec :prev-screen [_]
+ (s/keys :req-un []))
+
+(defmethod action-opts-spec :open-url [_]
+ (s/keys :req-un [::url]))
+
+(s/def ::action-opts
+ (s/multi-spec action-opts-spec ::action-type))
+
+;; -- Interaction
+
+(s/def ::classifier
+ (s/keys :req-un [::event-type
+ ::action-type]))
+
+(s/def ::interaction
+ (s/merge ::classifier
+ ::event-opts
+ ::action-opts))
+
+(s/def ::interactions
+ (s/coll-of ::interaction :kind vector?))
+
+(def default-interaction
+ {:event-type :click
+ :action-type :navigate
+ :destination nil})
+
+(def default-delay 100)
+
+;; -- Helpers
+
+(declare calc-overlay-position)
+
+(defn set-event-type
+ [interaction event-type]
+ (us/verify ::interaction interaction)
+ (us/verify ::event-type event-type)
+ (if (= (:event-type interaction) event-type)
+ interaction
+ (case event-type
+
+ :after-delay
+ (assoc interaction
+ :event-type event-type
+ :delay (get interaction :delay default-delay))
+
+ (assoc interaction
+ :event-type event-type))))
+
+
+(defn set-action-type
+ [interaction action-type shape objects]
+ (us/verify ::interaction interaction)
+ (us/verify ::action-type action-type)
+ (if (= (:action-type interaction) action-type)
+ interaction
+ (case action-type
+
+ :navigate
+ (assoc interaction
+ :action-type action-type
+ :destination (get interaction :destination))
+
+ :open-overlay
+ (let [destination (get interaction :destination)
+ overlay-pos-type (get interaction :overlay-pos-type :center)
+ overlay-position (get interaction
+ :overlay-position
+ (calc-overlay-position
+ destination
+ interaction
+ shape
+ objects
+ overlay-pos-type))]
+ (assoc interaction
+ :action-type action-type
+ :destination destination
+ :overlay-pos-type overlay-pos-type
+ :overlay-position overlay-position))
+
+ :close-overlay
+ (assoc interaction
+ :action-type action-type
+ :destination (get interaction :destination))
+
+ :prev-screen
+ (assoc interaction
+ :action-type action-type)
+
+ :open-url
+ (assoc interaction
+ :action-type action-type
+ :url (get interaction :url "")))))
+
+(defn set-destination
+ [interaction destination shape objects]
+ (us/verify ::interaction interaction)
+ (us/verify ::destination destination)
+ (assert (or (nil? destination)
+ (some? (get objects destination))))
+ (assert #(:navigate :open-overlay :close-overlay) (:action-type interaction))
+ (cond-> interaction
+ :always
+ (assoc :destination destination)
+
+ (= (:action-type interaction) :open-overlay)
+ (assoc :overlay-pos-type :center
+ :overlay-position (calc-overlay-position destination
+ interaction
+ shape
+ objects
+ :center))))
+
+(defn set-overlay-pos-type
+ [interaction overlay-pos-type shape objects]
+ (us/verify ::interaction interaction)
+ (us/verify ::overlay-pos-type overlay-pos-type)
+ (assert #(= :open-overlay (:action-type interaction)))
+ (assoc interaction
+ :overlay-pos-type overlay-pos-type
+ :overlay-position (calc-overlay-position (:destination interaction)
+ interaction
+ shape
+ objects
+ overlay-pos-type)))
+
+(defn toggle-overlay-pos-type
+ [interaction overlay-pos-type shape objects]
+ (us/verify ::interaction interaction)
+ (us/verify ::overlay-pos-type overlay-pos-type)
+ (assert #(= :open-overlay (:action-type interaction)))
+ (let [new-pos-type (if (= (:overlay-pos-type interaction) overlay-pos-type)
+ :manual
+ overlay-pos-type)]
+ (assoc interaction
+ :overlay-pos-type new-pos-type
+ :overlay-position (calc-overlay-position (:destination interaction)
+ interaction
+ shape
+ objects
+ new-pos-type))))
+
+(defn set-overlay-position
+ [interaction overlay-position]
+ (us/verify ::interaction interaction)
+ (us/verify ::overlay-position overlay-position)
+ (assert #(= :open-overlay (:action-type interaction)))
+ (assoc interaction
+ :overlay-pos-type :manual
+ :overlay-position overlay-position))
+
+(defn set-close-click-outside
+ [interaction close-click-outside]
+ (us/verify ::interaction interaction)
+ (us/verify ::us/boolean close-click-outside)
+ (assert #(= :open-overlay (:action-type interaction)))
+ (assoc interaction :close-click-outside close-click-outside))
+
+(defn set-background-overlay
+ [interaction background-overlay]
+ (us/verify ::interaction interaction)
+ (us/verify ::us/boolean background-overlay)
+ (assert #(= :open-overlay (:action-type interaction)))
+ (assoc interaction :background-overlay background-overlay))
+
+(defn- calc-overlay-position
+ [destination interaction shape objects overlay-pos-type]
+ (if (nil? destination)
+ (gpt/point 0 0)
+ (let [dest-frame (get objects destination)
+ overlay-size (:selrect dest-frame)
+ orig-frame (if (= (:type shape) :frame)
+ shape
+ (get objects (:frame-id shape)))
+ frame-size (:selrect orig-frame)]
+ (case overlay-pos-type
+
+ :center
+ (gpt/point (/ (- (:width frame-size) (:width overlay-size)) 2)
+ (/ (- (:height frame-size) (:height overlay-size)) 2))
+
+ :top-left
+ (gpt/point 0 0)
+
+ :top-right
+ (gpt/point (- (:width frame-size) (:width overlay-size))
+ 0)
+
+ :top-center
+ (gpt/point (/ (- (:width frame-size) (:width overlay-size)) 2)
+ 0)
+
+ :bottom-left
+ (gpt/point 0
+ (- (:height frame-size) (:height overlay-size)))
+
+ :bottom-right
+ (gpt/point (- (:width frame-size) (:width overlay-size))
+ (- (:height frame-size) (:height overlay-size)))
+
+ :bottom-center
+ (gpt/point (/ (- (:width frame-size) (:width overlay-size)) 2)
+ (- (:height frame-size) (:height overlay-size)))
+
+ :manual
+ (:overlay-position interaction)))))
+
diff --git a/frontend/resources/images/icons/position-bottom-center.svg b/frontend/resources/images/icons/position-bottom-center.svg
new file mode 100644
index 000000000..002466ead
--- /dev/null
+++ b/frontend/resources/images/icons/position-bottom-center.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/icons/position-bottom-left.svg b/frontend/resources/images/icons/position-bottom-left.svg
new file mode 100644
index 000000000..4811b74a9
--- /dev/null
+++ b/frontend/resources/images/icons/position-bottom-left.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/icons/position-bottom-right.svg b/frontend/resources/images/icons/position-bottom-right.svg
new file mode 100644
index 000000000..ebf861dcf
--- /dev/null
+++ b/frontend/resources/images/icons/position-bottom-right.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/icons/position-center.svg b/frontend/resources/images/icons/position-center.svg
new file mode 100644
index 000000000..ce6695ba7
--- /dev/null
+++ b/frontend/resources/images/icons/position-center.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/icons/position-top-center.svg b/frontend/resources/images/icons/position-top-center.svg
new file mode 100644
index 000000000..5a971d427
--- /dev/null
+++ b/frontend/resources/images/icons/position-top-center.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/images/icons/position-top-left.svg b/frontend/resources/images/icons/position-top-left.svg
new file mode 100644
index 000000000..0285e444e
--- /dev/null
+++ b/frontend/resources/images/icons/position-top-left.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/frontend/resources/images/icons/position-top-right.svg b/frontend/resources/images/icons/position-top-right.svg
new file mode 100644
index 000000000..838f63602
--- /dev/null
+++ b/frontend/resources/images/icons/position-top-right.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/resources/styles/main/layouts/viewer.scss b/frontend/resources/styles/main/layouts/viewer.scss
index 7e05a861b..296322332 100644
--- a/frontend/resources/styles/main/layouts/viewer.scss
+++ b/frontend/resources/styles/main/layouts/viewer.scss
@@ -43,3 +43,19 @@
grid-row: 1 / span 2;
}
}
+
+.viewer-overlay {
+ position: absolute;
+}
+
+.viewer-overlay-background {
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ &.visible {
+ background-color: rgb(0, 0, 0, 0.2);
+ }
+}
+
+
diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss
index f1845e3d9..5a36e748f 100644
--- a/frontend/resources/styles/main/partials/sidebar-element-options.scss
+++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss
@@ -222,6 +222,31 @@
}
}
+ .input-checkbox {
+ label {
+ color: $color-gray-20;
+ }
+
+ label::before {
+ background-color: transparent;
+ width: 16px;
+ height: 16px;
+ }
+
+ label::after {
+ width: 16px;
+ height: 16px;
+ }
+
+ input:checked + label::before {
+ border-width: 1px;
+ }
+
+ input:checked + label::after {
+ font-size: 0.8rem;
+ }
+ }
+
.element-set-subtitle {
color: $color-gray-20;
font-size: $fs11;
@@ -883,12 +908,31 @@
&.selected {
border: 1px solid $color-primary;
}
+
+ &:not(:first-child) {
+ margin-top: 7px;
+ }
+
+ &.open {
+ &:hover {
+ background: unset;
+ }
+ }
+}
+
+.interactions-options {
+ &.element-set {
+ border-bottom: 0;
+ }
+
+ .element-set-options-group {
+ flex-wrap: wrap;
+ }
}
.exports-options,
-.shadow-options{
+.shadow-options {
.element-set-options-group {
- justify-content: space-between;
.delete-icon {
display: flex;
min-width: 40px;
@@ -902,10 +946,6 @@
fill: $color-gray-20;
}
}
-
- &:not(:first-child) {
- margin-top: 7px;
- }
}
.download-button {
@@ -945,11 +985,15 @@
width: 12px;
height: 12px;
fill: $color-gray-20;
+ stroke: $color-gray-20;
}
- &:hover svg {
+ &:hover svg,
+ &.active svg {
fill: $color-primary;
+ stroke: $color-primary;
}
+
&.actions-inside {
position: absolute;
right: 0;
diff --git a/frontend/resources/styles/main/partials/sidebar-interactions.scss b/frontend/resources/styles/main/partials/sidebar-interactions.scss
index c8ea87266..c01779e32 100644
--- a/frontend/resources/styles/main/partials/sidebar-interactions.scss
+++ b/frontend/resources/styles/main/partials/sidebar-interactions.scss
@@ -7,8 +7,14 @@
.interactions-help {
font-size: $fs12;
- margin: 0 $medium;
+ padding: 7px $medium;
+ margin: 0 -7px;
text-align: center;
+
+ &.separator {
+ padding-bottom: $medium;
+ border-bottom: 1px solid $color-black;
+ }
}
.interactions-help-icon {
@@ -24,7 +30,9 @@
}
.interactions-summary {
- width: 100%;
+ cursor: pointer;
+ flex-basis: 0;
+ flex-grow: 1;
.trigger-name {
font-size: $fs12;
@@ -40,10 +48,34 @@
.interactions-element {
display: flex;
align-items: center;
+ margin: 0 -7px;
+ padding: 0 7px;
.element-label {
color: $color-gray-20;
font-size: $fs11;
width: 64px;
}
+
+ &.separator {
+ border-top: 1px solid $color-black;
+ margin-top: $x-small;
+ }
+}
+
+.interactions-pos-buttons {
+ margin-top: $small;
+ padding-top: $small;
+ padding-bottom: $small;
+ justify-content: space-between;
+
+ .element-set-actions-button {
+ min-width: 18px;
+ min-height: 18px;
+ }
+
+ svg {
+ width: 18px;
+ height: 18px;
+ }
}
diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs
index e438a54f5..75063ce04 100644
--- a/frontend/src/app/main/data/viewer.cljs
+++ b/frontend/src/app/main/data/viewer.cljs
@@ -328,8 +328,11 @@
;; --- Overlays
(defn open-overlay
- [frame-id]
+ [frame-id position close-click-outside background-overlay]
(us/verify ::us/uuid frame-id)
+ (us/verify ::us/point position)
+ (us/verify (s/nilable ::us/boolean) close-click-outside)
+ (us/verify (s/nilable ::us/boolean) background-overlay)
(ptk/reify ::open-overlay
ptk/UpdateEvent
(update [_ state]
@@ -340,7 +343,11 @@
frame (d/seek #(= (:id %) frame-id) frames)
overlays (get-in state [:viewer-local :overlays])]
(if-not (some #(= % frame) overlays)
- (update-in state [:viewer-local :overlays] conj frame)
+ (update-in state [:viewer-local :overlays] conj
+ {:frame frame
+ :position position
+ :close-click-outside close-click-outside
+ :background-overlay background-overlay})
state)))))
(defn close-overlay
@@ -350,7 +357,7 @@
(update [_ state]
(update-in state [:viewer-local :overlays]
(fn [overlays]
- (remove #(= (:id %) frame-id) overlays))))))
+ (remove #(= (:id (:frame %)) frame-id) overlays))))))
;; --- Objects selection
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index 362a461f3..a53fdcee3 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -18,6 +18,7 @@
[app.common.pages.spec :as spec]
[app.common.spec :as us]
[app.common.transit :as t]
+ [app.common.types.interactions :as cti]
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.main.data.events :as ev]
@@ -1826,10 +1827,93 @@
frame)]
;; Update or create interaction
(if index
- (assoc-in interactions [index :destination] (:id frame))
+ (update interactions index
+ #(cti/set-destination % (:id frame) shape objects))
(conj (or interactions [])
- (assoc spec/default-interaction
- :destination (:id frame))))))))))))))))
+ (cti/set-destination cti/default-interaction
+ (:id frame)
+ shape
+ objects)))))))))))))))
+
+(declare move-overlay-pos)
+(declare finish-move-overlay-pos)
+
+(defn start-move-overlay-pos
+ [index]
+ (ptk/reify ::start-move-overlay-pos
+ ptk/UpdateEvent
+ (update [_ state]
+ (-> state
+ (assoc-in [:workspace-local :move-overlay-to] nil)
+ (assoc-in [:workspace-local :move-overlay-index] index)))
+
+ ptk/WatchEvent
+ (watch [_ state stream]
+ (let [initial-pos @ms/mouse-position
+ selected (wsh/lookup-selected state)
+ stopper (rx/filter ms/mouse-up? stream)]
+ (when (= 1 (count selected))
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ shape (->> state
+ wsh/lookup-selected
+ first
+ (get objects))
+ overlay-pos (-> shape
+ (get-in [:interactions index])
+ :overlay-position)
+ orig-frame (cph/get-frame shape objects)
+ frame-pos (gpt/point (:x orig-frame) (:y orig-frame))
+ offset (-> initial-pos
+ (gpt/subtract overlay-pos)
+ (gpt/subtract frame-pos))]
+ (rx/concat
+ (->> ms/mouse-position
+ (rx/take-until stopper)
+ (rx/map #(move-overlay-pos % frame-pos offset)))
+ (rx/of (finish-move-overlay-pos index frame-pos offset)))))))))
+
+(defn move-overlay-pos
+ [pos frame-pos offset]
+ (ptk/reify ::move-overlay-pos
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [pos (-> pos
+ (gpt/subtract frame-pos)
+ (gpt/subtract offset))]
+ (assoc-in state [:workspace-local :move-overlay-to] pos)))))
+
+(defn finish-move-overlay-pos
+ [index frame-pos offset]
+ (ptk/reify ::finish-move-overlay-pos
+ ptk/UpdateEvent
+ (update [_ state]
+ (-> state
+ (d/dissoc-in [:workspace-local :move-overlay-to])
+ (d/dissoc-in [:workspace-local :move-overlay-index])))
+
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [pos @ms/mouse-position
+ overlay-pos (-> pos
+ (gpt/subtract frame-pos)
+ (gpt/subtract offset))
+
+ page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ shape (->> state
+ wsh/lookup-selected
+ first
+ (get objects))
+
+ interactions (:interactions shape)
+
+ new-interactions
+ (update interactions index
+ #(cti/set-overlay-position % overlay-pos))]
+
+ (rx/of (update-shape (:id shape) {:interactions new-interactions}))))))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CANVAS OPTIONS
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index 47738e45a..abf60ef6e 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -101,6 +101,13 @@
(def play (icon-xref :play))
(def plus (icon-xref :plus))
(def pointer-inner (icon-xref :pointer-inner))
+(def position-bottom-center (icon-xref :position-bottom-center))
+(def position-bottom-left (icon-xref :position-bottom-left))
+(def position-bottom-right (icon-xref :position-bottom-right))
+(def position-center (icon-xref :position-center))
+(def position-top-center (icon-xref :position-top-center))
+(def position-top-left (icon-xref :position-top-left))
+(def position-top-right (icon-xref :position-top-right))
(def radius (icon-xref :radius))
(def radius-1 (icon-xref :radius-1))
(def radius-4 (icon-xref :radius-4))
diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs
index c98628bb4..e84fc0361 100644
--- a/frontend/src/app/main/ui/viewer.cljs
+++ b/frontend/src/app/main/ui/viewer.cljs
@@ -33,11 +33,6 @@
:height (* height zoom)
:vbox (str "0 0 " width " " height)}))
-(defn- position-overlay
- [size size-over]
- {:x (/ (- (:width size) (:width size-over)) 2)
- :y (/ (- (:height size) (:height size-over)) 2)})
-
(mf/defc viewer
[{:keys [params data]}]
@@ -70,7 +65,12 @@
(mf/deps section)
(fn [_]
(when (= section :comments)
- (st/emit! (dcm/close-thread)))))]
+ (st/emit! (dcm/close-thread)))))
+
+ close-overlay
+ (mf/use-callback
+ (fn [frame]
+ (st/emit! (dv/close-overlay (:id frame)))))]
(hooks/use-shortcuts ::viewer sc/shortcuts)
@@ -125,7 +125,6 @@
:section section
:local local}]
-
[:div.viewport-container
{:style {:width (:width size)
:height (:height size)
@@ -147,21 +146,32 @@
:local local}]
(for [overlay (:overlays local)]
- (let [size-over (calculate-size overlay zoom)
- pos-over (position-overlay size size-over)]
- [:div.viewport-container
- {:style {:width (:width size-over)
- :height (:height size-over)
- :position "absolute"
- :left (:x pos-over)
- :top (:y pos-over)}}
- [:& interactions/viewport
- {:frame overlay
- :size size-over
- :page page
- :file file
- :users users
- :local local}]]))]))]]]))
+ (let [size-over (calculate-size (:frame overlay) zoom)]
+ [:*
+ (when (or (:close-click-outside overlay)
+ (:background-overlay overlay))
+ [:div.viewer-overlay-background
+ {:class (dom/classnames
+ :visible (:background-overlay overlay))
+ :style {:width (:width frame)
+ :height (:height frame)
+ :position "absolute"
+ :left 0
+ :top 0}
+ :on-click #(when (:close-click-outside overlay)
+ (close-overlay (:frame overlay)))}])
+ [:div.viewport-container.viewer-overlay
+ {:style {:width (:width size-over)
+ :height (:height size-over)
+ :left (* (:x (:position overlay)) zoom)
+ :top (* (:y (:position overlay)) zoom)}}
+ [:& interactions/viewport
+ {:frame (:frame overlay)
+ :size size-over
+ :page page
+ :file file
+ :users users
+ :local local}]]]))]))]]]))
;; --- Component: Viewer Page
diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs
index d4850808c..a39d11894 100644
--- a/frontend/src/app/main/ui/viewer/shapes.cljs
+++ b/frontend/src/app/main/ui/viewer/shapes.cljs
@@ -39,9 +39,15 @@
(st/emit! (dv/go-to-frame frame-id)))
:open-overlay
- (let [frame-id (:destination interaction)]
+ (let [frame-id (:destination interaction)
+ position (:overlay-position interaction)
+ close-click-outside (:close-click-outside interaction)
+ background-overlay (:background-overlay interaction)]
(dom/stop-propagation event)
- (st/emit! (dv/open-overlay frame-id)))
+ (st/emit! (dv/open-overlay frame-id
+ position
+ close-click-outside
+ background-overlay)))
:close-overlay
(let [frame-id (or (:destination interaction)
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
index 0f9b20400..545eea731 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
@@ -8,7 +8,7 @@
(:require
[app.common.data :as d]
[app.common.pages :as cp]
- [app.common.pages.spec :as spec]
+ [app.common.types.interactions :as cti]
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
@@ -21,7 +21,7 @@
(defn- event-type-names
[]
{:click (tr "workspace.options.interaction-on-click")
- :hover (tr "workspace.options.interaction-while-hovering")})
+ :mouse-over (tr "workspace.options.interaction-while-hovering")})
(defn- event-type-name
[interaction]
@@ -31,7 +31,8 @@
[]
{:navigate (tr "workspace.options.interaction-navigate-to")
:open-overlay (tr "workspace.options.interaction-open-overlay")
- :close-overlay (tr "workspace.options.interaction-close-overlay")})
+ :close-overlay (tr "workspace.options.interaction-close-overlay")
+ :prev-screen (tr "workspace.options.interaction-prev-screen")})
(defn- action-summary
[interaction destination]
@@ -44,6 +45,17 @@
(get destination :name (tr "workspace.options.interaction-self")))
"--"))
+(defn- overlay-pos-type-names
+ []
+ {:manual (tr "workspace.options.interaction-pos-manual")
+ :center (tr "workspace.options.interaction-pos-center")
+ :top-left (tr "workspace.options.interaction-pos-top-left")
+ :top-right (tr "workspace.options.interaction-pos-top-right")
+ :top-center (tr "workspace.options.interaction-pos-top-center")
+ :bottom-left (tr "workspace.options.interaction-pos-bottom-left")
+ :bottom-right (tr "workspace.options.interaction-pos-bottom-right")
+ :bottom-center (tr "workspace.options.interaction-pos-bottom-center")})
+
(mf/defc interaction-entry
[{:keys [index shape interaction update-interaction remove-interaction]}]
(let [objects (deref refs/workspace-page-objects)
@@ -51,60 +63,139 @@
frames (mf/use-memo (mf/deps objects)
#(cp/select-frames objects))
+ action-type (:action-type interaction)
+ overlay-pos-type (:overlay-pos-type interaction)
+ close-click-outside? (:close-click-outside interaction false)
+ background-overlay? (:background-overlay interaction false)
+
extended-open? (mf/use-state false)
change-event-type
(fn [event]
(let [value (-> event dom/get-target dom/get-value d/read-string)]
- (update-interaction index #(assoc % :event-type value))))
+ (update-interaction index #(cti/set-event-type % value))))
change-action-type
(fn [event]
(let [value (-> event dom/get-target dom/get-value d/read-string)]
- (update-interaction index #(assoc % :action-type value))))
+ (update-interaction index #(cti/set-action-type % value shape objects))))
change-destination
(fn [event]
(let [value (-> event dom/get-target dom/get-value)
value (when (not= value "") (uuid/uuid value))]
- (update-interaction index #(assoc % :destination value))))]
+ (update-interaction index #(cti/set-destination % value shape objects))))
+
+ change-overlay-pos-type
+ (fn [event]
+ (let [value (-> event dom/get-target dom/get-value d/read-string)]
+ (update-interaction index #(cti/set-overlay-pos-type % value shape objects))))
+
+ toggle-overlay-pos-type
+ (fn [pos-type]
+ (update-interaction index #(cti/toggle-overlay-pos-type % pos-type shape objects)))
+
+ change-close-click-outside
+ (fn [event]
+ (let [value (-> event dom/get-target dom/checked?)]
+ (update-interaction index #(cti/set-close-click-outside % value))))
+
+ change-background-overlay
+ (fn [event]
+ (let [value (-> event dom/get-target dom/checked?)]
+ (update-interaction index #(cti/set-background-overlay % value))))]
[:*
- [:div.element-set-options-group
+ [:div.element-set-options-group {:class (dom/classnames
+ :open @extended-open?)}
[:div.element-set-actions-button {:on-click #(swap! extended-open? not)}
i/actions]
- [:div.interactions-summary
+ [:div.interactions-summary {:on-click #(swap! extended-open? not)}
[:div.trigger-name (event-type-name interaction)]
[:div.action-summary (action-summary interaction destination)]]
[:div.elemen-set-actions {:on-click #(remove-interaction index)}
- [:div.element-set-actions-button i/minus]]]
- (when @extended-open?
- [:div.element-set
+ [:div.element-set-actions-button i/minus]]
+ (when @extended-open?
[:div.element-set-content
- [:div.interactions-element
- [:span.element-set-subtitle.wide (tr "workspace.options.interaction-trigger")]
- [:select.input-select
- {:default-value (str (:event-type interaction))
- :on-change change-event-type}
- (for [[value name] (event-type-names)]
- [:option {:value (str value)} name])]]
- [:div.interactions-element
- [:span.element-set-subtitle.wide (tr "workspace.options.interaction-action")]
- [:select.input-select
- {:default-value (str (:action-type interaction))
- :on-change change-action-type}
- (for [[value name] (action-type-names)]
- [:option {:value (str value)} name])]]
- [:div.interactions-element
+ [:div.interactions-element.separator
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-trigger")]
+ [:select.input-select
+ {:value (str (:event-type interaction))
+ :on-change change-event-type}
+ (for [[value name] (event-type-names)]
+ [:option {:value (str value)} name])]]
+ [:div.interactions-element.separator
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-action")]
+ [:select.input-select
+ {:value (str (:action-type interaction))
+ :on-change change-action-type}
+ (for [[value name] (action-type-names)]
+ [:option {:value (str value)} name])]]
+ (when (#{:navigate :open-overlay :close-overlay} action-type)
+ [:div.interactions-element
[:span.element-set-subtitle.wide (tr "workspace.options.interaction-destination")]
[:select.input-select
- {:default-value (str (:destination interaction))
+ {:value (str (:destination interaction))
:on-change change-destination}
[:option {:value ""} (tr "workspace.options.interaction-none")]
(for [frame frames]
(when (and (not= (:id frame) (:id shape)) ; A frame cannot navigate to itself
(not= (:id frame) (:frame-id shape))) ; nor a shape to its container frame
- [:option {:value (str (:id frame))} (:name frame)]))]]]])]))
+ [:option {:value (str (:id frame))} (:name frame)]))]])
+ (when (= action-type :open-overlay)
+ [:*
+ [:div.interactions-element
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-position")]
+ [:select.input-select
+ {:value (str (:overlay-pos-type interaction))
+ :on-change change-overlay-pos-type}
+ (for [[value name] (overlay-pos-type-names)]
+ [:option {:value (str value)} name])]]
+ [:div.interactions-element.interactions-pos-buttons
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :center))
+ :on-click #(toggle-overlay-pos-type :center)}
+ i/position-center]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :top-left))
+ :on-click #(toggle-overlay-pos-type :top-left)}
+ i/position-top-left]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :top-right))
+ :on-click #(toggle-overlay-pos-type :top-right)}
+ i/position-top-right]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :top-center))
+ :on-click #(toggle-overlay-pos-type :top-center)}
+ i/position-top-center]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :bottom-left))
+ :on-click #(toggle-overlay-pos-type :bottom-left)}
+ i/position-bottom-left]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :bottom-right))
+ :on-click #(toggle-overlay-pos-type :bottom-right)}
+ i/position-bottom-right]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= overlay-pos-type :bottom-center))
+ :on-click #(toggle-overlay-pos-type :bottom-center)}
+ i/position-bottom-center]]
+ [:div.interactions-element
+ [:div.input-checkbox
+ [:input {:type "checkbox"
+ :id (str "close-" index)
+ :checked close-click-outside?
+ :on-change change-close-click-outside}]
+ [:label {:for (str "close-" index)}
+ (tr "workspace.options.interaction-close-outside")]]]
+ [:div.interactions-element
+ [:div.input-checkbox
+ [:input {:type "checkbox"
+ :id (str "background-" index)
+ :checked background-overlay?
+ :on-change change-background-overlay}]
+ [:label {:for (str "background-" index)}
+ (tr "workspace.options.interaction-background")]]]])])]]))
(mf/defc interactions-menu
[{:keys [shape] :as props}]
@@ -112,8 +203,7 @@
add-interaction
(fn [_]
- (let [new-interactions
- (conj interactions (update spec/default-interaction :event-type identity))]
+ (let [new-interactions (conj interactions cti/default-interaction)]
(st/emit! (dw/update-shape (:id shape) {:interactions new-interactions}))))
remove-interaction
@@ -128,7 +218,7 @@
(let [new-interactions (update interactions index update-fn)]
(st/emit! (dw/update-shape (:id shape) {:interactions new-interactions})))) ]
- [:div.element-set
+ [:div.element-set.interactions-options
(when shape
[:div.element-set-title
[:span (tr "workspace.options.interactions")]
@@ -141,15 +231,16 @@
(when shape
[:*
[:div.interactions-help-icon i/plus]
- [:div.interactions-help (tr "workspace.options.add-interaction")]])
+ [:div.interactions-help.separator (tr "workspace.options.add-interaction")]])
[:div.interactions-help-icon i/interaction]
[:div.interactions-help (tr "workspace.options.select-a-shape")]
[:div.interactions-help-icon i/play]
[:div.interactions-help (tr "workspace.options.use-play-button")]])]
- (for [[index interaction] (d/enumerate interactions)]
- [:& interaction-entry {:index index
- :shape shape
- :interaction interaction
- :update-interaction update-interaction
- :remove-interaction remove-interaction}])]))
+ [:div.groups
+ (for [[index interaction] (d/enumerate interactions)]
+ [:& interaction-entry {:index index
+ :shape shape
+ :interaction interaction
+ :update-interaction update-interaction
+ :remove-interaction remove-interaction}])]]))
diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
index aaa267bd5..e041bd773 100644
--- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
@@ -8,6 +8,7 @@
"Visually show shape interactions in workspace"
(:require
[app.common.data :as d]
+ [app.common.pages.helpers :as cph]
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -86,11 +87,8 @@
:right "M -5 0 l 8 0 l -4 -4 m 4 4 l -4 4"
:left "M 5 0 l -8 0 l 4 -4 m -4 4 l 4 4"
nil)
- :open-overlay (case arrow-dir
- ;; TODO: have a different icon for open overlay?
- :right "M -5 0 l 8 0 l -4 -4 m 4 4 l -4 4"
- :left "M 5 0 l -8 0 l 4 -4 m -4 4 l 4 4"
- nil)
+
+ :open-overlay "M-4 -4 h6 v6 h-6 z M2 -2 h2.5 v6.5 h-6.5 v-2.5"
:close-overlay "M -4 -4 L 4 4 M -4 4 L 4 -4"
@@ -99,17 +97,15 @@
[:*
[:circle {:cx 0
:cy 0
- :r 8
- :stroke stroke
- :stroke-width 2
- :fill "#FFFFFF"
+ :r (if (some? action-type) 8 4)
+ :fill stroke
:transform (str
"scale(" inv-zoom ", " inv-zoom ") "
"translate(" (* zoom x) ", " (* zoom y) ")")}]
(when icon-pdata
- [:path {:stroke stroke
- :fill "none"
- :stroke-width 2
+ [:path {:fill stroke
+ :stroke-width 1
+ :stroke "#FFFFFF"
:d icon-pdata
:transform (str
"scale(" inv-zoom ", " inv-zoom ") "
@@ -117,7 +113,7 @@
(mf/defc interaction-path
- [{:keys [index orig-shape dest-shape dest-point selected? action-type zoom] :as props}]
+ [{:keys [index level orig-shape dest-shape dest-point selected? action-type zoom] :as props}]
(let [[orig-pos orig-x orig-y dest-pos dest-x dest-y]
(cond
dest-shape
@@ -129,7 +125,8 @@
:else
(connect-to-point orig-shape
{:x (+ (:x2 (:selrect orig-shape)) 100)
- :y (- (:y1 (:selrect orig-shape)) 50)}))
+ :y (+ (- (:y1 (:selrect orig-shape)) 50)
+ (* level 16))}))
orig-dx (if (= orig-pos :right) 100 -100)
dest-dx (if (= dest-pos :right) 100 -100)
@@ -146,8 +143,7 @@
:pointer-events "visible"
:stroke-width (/ 2 zoom)
:d pdata}]
- (when (and (not dest-shape)
- (= action-type :close-overlay))
+ (when (not dest-shape)
[:& interaction-marker {:index index
:x dest-x
:y dest-y
@@ -162,6 +158,11 @@
:pointer-events "visible"
:stroke-width (/ 2 zoom)
:d pdata}]
+
+ (when dest-shape
+ [:& outline {:shape dest-shape
+ :color "#31EFB8"}])
+
[:& interaction-marker {:index index
:x orig-x
:y orig-y
@@ -173,11 +174,7 @@
:stroke "#31EFB8"
:action-type action-type
:arrow-dir arrow-dir
- :zoom zoom}]
-
- (when dest-shape
- [:& outline {:shape dest-shape
- :color "#31EFB8"}])])))
+ :zoom zoom}]])))
(mf/defc interaction-handle
@@ -194,6 +191,37 @@
:zoom zoom}]]))
+(mf/defc overlay-marker
+ [{:keys [index orig-shape dest-shape position objects] :as props}]
+ (let [start-move-position
+ (fn [_]
+ (st/emit! (dw/start-move-overlay-pos index)))]
+
+ (when dest-shape
+ (let [orig-frame (cph/get-frame orig-shape objects)
+ marker-x (+ (:x orig-frame) (:x position))
+ marker-y (+ (:y orig-frame) (:y position))
+ width (:width dest-shape)
+ height (:height dest-shape)]
+ [:g {:on-mouse-down start-move-position}
+ [:path {:stroke "#31EFB8"
+ :fill "#000000"
+ :fill-opacity 0.3
+ :stroke-width 1
+ :d (str "M" marker-x " " marker-y " "
+ "h " width " "
+ "v " height " "
+ "h -" width " z"
+ "M" marker-x " " marker-y " "
+ "l " width " " height " "
+ "M" marker-x " " (+ marker-y height) " "
+ "l " width " -" height " ")}]
+ [:circle {:cx (+ marker-x (/ width 2))
+ :cy (+ marker-y (/ height 2))
+ :r 8
+ :fill "#31EFB8"}]
+ ]))))
+
(mf/defc interactions
[{:keys [selected] :as props}]
(let [local (mf/deref refs/workspace-local)
@@ -205,17 +233,26 @@
editing-interaction-index (:editing-interaction-index local)
draw-interaction-to (:draw-interaction-to local)
draw-interaction-to-frame (:draw-interaction-to-frame local)
- first-selected (first selected-shapes)]
+ move-overlay-to (:move-overlay-to local)
+ move-overlay-index (:move-overlay-index local)
+ first-selected (first selected-shapes)
+
+ calc-level (fn [index interactions]
+ (->> (subvec interactions 0 index)
+ (filter #(nil? (:destination %)))
+ (count)))]
[:g.interactions
[:g.non-selected
(for [shape active-shapes]
(for [[index interaction] (d/enumerate (:interactions shape))]
(let [dest-shape (get objects (:destination interaction))
- selected? (contains? selected (:id shape))]
+ selected? (contains? selected (:id shape))
+ level (calc-level index (:interactions shape))]
(when-not selected?
[:& interaction-path {:key (str (:id shape) "-" index)
:index index
+ :level level
:orig-shape shape
:dest-shape dest-shape
:selected selected
@@ -237,15 +274,33 @@
(if (seq (:interactions shape))
(for [[index interaction] (d/enumerate (:interactions shape))]
(when-not (= index editing-interaction-index)
- (let [dest-shape (get objects (:destination interaction))]
- [:& interaction-path {:key (str (:id shape) "-" index)
- :index index
- :orig-shape shape
- :dest-shape dest-shape
- :selected selected
- :selected? true
- :action-type (:action-type interaction)
- :zoom zoom}])))
+ (let [dest-shape (get objects (:destination interaction))
+ level (calc-level index (:interactions shape))]
+ [:*
+ [:& interaction-path {:key (str (:id shape) "-" index)
+ :index index
+ :level level
+ :orig-shape shape
+ :dest-shape dest-shape
+ :selected selected
+ :selected? true
+ :action-type (:action-type interaction)
+ :zoom zoom}]
+ (when (= (:action-type interaction) :open-overlay)
+ (if (and (some? move-overlay-to)
+ (= move-overlay-index index))
+ [:& overlay-marker {:key (str "pos" (:id shape) "-" index)
+ :index index
+ :orig-shape shape
+ :dest-shape dest-shape
+ :position move-overlay-to
+ :objects objects}]
+ [:& overlay-marker {:key (str "pos" (:id shape) "-" index)
+ :index index
+ :orig-shape shape
+ :dest-shape dest-shape
+ :position (:overlay-position interaction)
+ :objects objects}]))])))
(when (not (#{:move :rotate} current-transform))
[:& interaction-handle {:key (:id shape)
:index nil
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index 0ed54d467..2a28ffd7a 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -2405,6 +2405,14 @@ msgstr "Click the + button to add interactions."
msgid "workspace.options.interaction-action"
msgstr "Action"
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-background"
+msgstr "Add background overlay"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-close-outside"
+msgstr "Close when clicking outside"
+
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.interaction-close-overlay"
msgstr "Close overlay"
@@ -2441,6 +2449,42 @@ msgstr "Open overlay"
msgid "workspace.options.interaction-open-overlay-dest"
msgstr "Open overlay: %s"
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-manual"
+msgstr "Manual"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-center"
+msgstr "Center"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-left"
+msgstr "Top left"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-right"
+msgstr "Top right"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-center"
+msgstr "Top center"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-left"
+msgstr "Bottom left"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-right"
+msgstr "Bottom right"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-center"
+msgstr "Bottom center"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-position"
+msgstr "Position"
+
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.interaction-self"
msgstr "self"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index 83b47aefe..344baeabd 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -2288,6 +2288,14 @@ msgstr "Pulsa el botón + para añadir interacciones."
msgid "workspace.options.interaction-action"
msgstr "Acción"
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-background"
+msgstr "Añadir sombreado de fondo"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-close-outside"
+msgstr "Cerrar al pulsar fuera"
+
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.interaction-close-overlay"
msgstr "Close overlay"
@@ -2324,6 +2332,42 @@ msgstr "Open overlay"
msgid "workspace.options.interaction-open-overlay-dest"
msgstr "Open overlay: %s"
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-manual"
+msgstr "Manual"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-center"
+msgstr "Centro"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-left"
+msgstr "Arriba izquierda"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-right"
+msgstr "Arriba derecha"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-top-center"
+msgstr "Arriba centro"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-left"
+msgstr "Abajo izquierda"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-right"
+msgstr "Abajo derecha"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-pos-bottom-center"
+msgstr "Abajo centro"
+
+#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+msgid "workspace.options.interaction-position"
+msgstr "Posición"
+
#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
msgid "workspace.options.interaction-self"
msgstr "self"