diff --git a/frontend/src/app/main/data/workspace/interactions.cljs b/frontend/src/app/main/data/workspace/interactions.cljs index a1f72393b..2fb10ada8 100644 --- a/frontend/src/app/main/data/workspace/interactions.cljs +++ b/frontend/src/app/main/data/workspace/interactions.cljs @@ -28,29 +28,33 @@ ;; --- Flows (defn add-flow - [starting-frame] + ([starting-frame] + (add-flow nil nil nil starting-frame)) - (dm/assert! - "expect uuid" - (uuid? starting-frame)) + ([flow-id page-id name starting-frame] + (dm/assert! + "expect uuid" + (uuid? starting-frame)) - (ptk/reify ::add-flow - ptk/WatchEvent - (watch [it state _] - (let [page (wsh/lookup-page state) + (ptk/reify ::add-flow + ptk/WatchEvent + (watch [it state _] + (let [page (if page-id + (wsh/lookup-page state page-id) + (wsh/lookup-page state)) - flows (get-in page [:options :flows] []) - unames (cfh/get-used-names flows) - name (cfh/generate-unique-name unames "Flow 1") + flows (get-in page [:options :flows] []) + unames (cfh/get-used-names flows) + name (or name (cfh/generate-unique-name unames "Flow 1")) - new-flow {:id (uuid/next) - :name name - :starting-frame starting-frame}] + new-flow {:id (or flow-id (uuid/next)) + :name name + :starting-frame starting-frame}] - (rx/of (dch/commit-changes - (-> (pcb/empty-changes it) - (pcb/with-page page) - (pcb/update-page-option :flows ctp/add-flow new-flow)))))))) + (rx/of (dch/commit-changes + (-> (pcb/empty-changes it) + (pcb/with-page page) + (pcb/update-page-option :flows ctp/add-flow new-flow))))))))) (defn add-flow-selected-frame [] @@ -61,16 +65,35 @@ (rx/of (add-flow (first selected))))))) (defn remove-flow - [flow-id] + ([flow-id] + (remove-flow nil flow-id)) + + ([page-id flow-id] + (dm/assert! (uuid? flow-id)) + (ptk/reify ::remove-flow + ptk/WatchEvent + (watch [it state _] + (let [page (if page-id + (wsh/lookup-page state page-id) + (wsh/lookup-page state))] + (rx/of (dch/commit-changes + (-> (pcb/empty-changes it) + (pcb/with-page page) + (pcb/update-page-option :flows ctp/remove-flow flow-id))))))))) + +(defn update-flow + [page-id flow-id update-fn] (dm/assert! (uuid? flow-id)) - (ptk/reify ::remove-flow + (ptk/reify ::update-flow ptk/WatchEvent (watch [it state _] - (let [page (wsh/lookup-page state)] + (let [page (if page-id + (wsh/lookup-page state page-id) + (wsh/lookup-page state))] (rx/of (dch/commit-changes (-> (pcb/empty-changes it) (pcb/with-page page) - (pcb/update-page-option :flows ctp/remove-flow flow-id)))))))) + (pcb/update-page-option :flows ctp/update-flow flow-id update-fn)))))))) (defn rename-flow [flow-id name] @@ -111,6 +134,18 @@ (or (some ctsi/flow-origin? (map :interactions children)) (some #(ctsi/flow-to? % frame-id) (map :interactions (vals objects)))))) +(defn add-interaction + [page-id shape-id interaction] + (ptk/reify ::add-interaction + ptk/WatchEvent + (watch [_ state _] + (let [page-id (or page-id (:current-page-id state))] + (rx/of (dwsh/update-shapes + [shape-id] + (fn [shape] + (cls/add-new-interaction shape interaction)) + {:page-id page-id})))))) + (defn add-new-interaction ([shape] (add-new-interaction shape nil)) ([shape destination] @@ -138,23 +173,29 @@ (rx/of (add-flow (:id frame)))))))))) (defn remove-interaction - [shape index] - (ptk/reify ::remove-interaction - ptk/WatchEvent - (watch [_ _ _] - (rx/of (dwsh/update-shapes [(:id shape)] - (fn [shape] - (update shape :interactions - ctsi/remove-interaction index))))))) + ([shape index] + (remove-interaction nil shape index)) + ([page-id shape index] + (ptk/reify ::remove-interaction + ptk/WatchEvent + (watch [_ _ _] + (rx/of (dwsh/update-shapes [(:id shape)] + (fn [shape] + (update shape :interactions + ctsi/remove-interaction index)) + {:page-id page-id})))))) (defn update-interaction - [shape index update-fn] - (ptk/reify ::update-interaction - ptk/WatchEvent - (watch [_ _ _] - (rx/of (dwsh/update-shapes [(:id shape)] - (fn [shape] - (update shape :interactions - ctsi/update-interaction index update-fn))))))) + ([shape index update-fn] + (update-interaction shape index update-fn nil)) + ([shape index update-fn options] + (ptk/reify ::update-interaction + ptk/WatchEvent + (watch [_ _ _] + (rx/of (dwsh/update-shapes [(:id shape)] + (fn [shape] + (update shape :interactions + ctsi/update-interaction index update-fn)) + options)))))) (defn remove-all-interactions-nav-to "Remove all interactions that navigate to the given frame." diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 27c149d8d..dc1eed755 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -352,7 +352,7 @@ (openViewer [_] - (let [params {:page-id (:current-page-id @st/state) + (let [params {:page-id (:current-page-id @st/state) :file-id (:current-file-id @st/state) :section "interactions"}] (st/emit! (dw/go-to-viewer params)))) diff --git a/frontend/src/app/plugins/format.cljs b/frontend/src/app/plugins/format.cljs index 4f6047df3..42d116b39 100644 --- a/frontend/src/app/plugins/format.cljs +++ b/frontend/src/app/plugins/format.cljs @@ -10,6 +10,8 @@ [app.common.data.macros :as dm] [app.util.object :as obj])) +(def shape-proxy nil) + (defn format-id [id] (when id (dm/str id))) @@ -422,3 +424,163 @@ [tracks] (when (some? tracks) (format-array format-track tracks))) + + +;; export interface PenpotDissolve { +;; type: 'dissolve'; +;; duration: number; +;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'; +;; } +;; +;; export interface PenpotSlide { +;; type: 'slide'; +;; way: 'in' | 'out'; +;; direction?: +;; | 'right' +;; | 'left' +;; | 'up' +;; | 'down'; +;; duration: number; +;; offsetEffect?: boolean; +;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'; +;; } +;; +;; export interface PenpotPush { +;; type: 'push'; +;; direction?: +;; | 'right' +;; | 'left' +;; | 'up' +;; | 'down'; +;; +;; duration: number; +;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'; +;; } +;; +;; export type PenpotAnimation = PenpotDissolve | PenpotSlide | PenpotPush; + +(defn format-animation + [animation] + (when animation + (obj/clear-empty + (case (:animation-type animation) + + :dissolve + #js {:type "dissolve" + :duration (:duration animation) + :easing (format-key (:easing animation))} + + :slide + #js {:type "slide" + :way (format-key (:way animation)) + :direction (format-key (:direction animation)) + :duration (:duration animation) + :easing (format-key (:easing animation)) + :offsetEffect (:offset-effect animation)} + + :push + #js {:type "push" + :direction (format-key (:direction animation)) + :duration (:duration animation) + :easing (format-key (:easing animation))} + nil)))) + +;;export type PenpotAction = +;; | PenpotNavigateTo +;; | PenpotOpenOverlay +;; | PenpotToggleOverlay +;; | PenpotCloseOverlay +;; | PenpotPreviousScreen +;; | PenpotOpenUrl; +;; +;;export interface PenpotNavigateTo { +;; type: 'navigate-to'; +;; destination: PenpotFrame; +;; preserveScrollPosition?: boolean; +;; animation: PenpotAnimation; +;;} +;; +;;export interface PenpotOverlayAction { +;; destination: PenpotFrame; +;; relativeTo?: PenpotShape; +;; position?: +;; | 'manual' +;; | 'center' +;; | 'top-left' +;; | 'top-right' +;; | 'top-center' +;; | 'bottom-left' +;; | 'bottom-right' +;; | 'bottom-center'; +;; manualPositionLocation?: PenpotPoint; +;; closeWhenClickOutside?: boolean; +;; addBackgroundOverlay?: boolean; +;; animation: PenpotAnimation; +;;} +;; +;;export interface PenpotOpenOverlay extends PenpotOverlayAction { +;; type: 'open-overlay'; +;;} +;; +;;export interface PenpotToggleOverlay extends PenpotOverlayAction { +;; type: 'toggle-overlay'; +;;} +;; +;;export interface PenpotCloseOverlay { +;; type: 'close-overlay'; +;; destination?: PenpotFrame; +;; animation: PenpotAnimation; +;;} +;; +;;export interface PenpotPreviousScreen { +;; type: 'previous-screen'; +;;} +;; +;;export interface PenpotOpenUrl { +;; type: 'open-url'; +;; url: string; +;;} +(defn format-action + [interaction plugin file-id page-id] + (when interaction + (obj/clear-empty + (case (:action-type interaction) + :navigate + #js {:type "navigate-to" + :destination (when (:destination interaction) (shape-proxy plugin file-id page-id (:destination interaction))) + :preserveScrollPosition (:preserve-scroll interaction false) + :animation (format-animation (:animation interaction))} + + :open-overlay + #js {:type "open-overlay" + :destination (when (:destination interaction) (shape-proxy plugin file-id page-id (:destination interaction))) + :relativeTo (when (:relative-to interaction) (shape-proxy plugin file-id page-id (:relative-to interaction))) + :position (format-key (:overlay-pos-type interaction)) + :manualPositionLocation (format-point (:overlay-position interaction)) + :closeWhenClickOutside (:close-click-outside interaction) + :addBackgroundOverlay (:background-overlay interaction) + :animation (format-animation (:animation interaction))} + + :toggle-overlay + #js {:type "toggle-overlay" + :destination (when (:destination interaction) (shape-proxy plugin file-id page-id (:destination interaction))) + :relativeTo (when (:relative-to interaction) (shape-proxy plugin file-id page-id (:relative-to interaction))) + :position (format-key (:overlay-pos-type interaction)) + :manualPositionLocation (format-point (:overlay-position interaction)) + :closeWhenClickOutside (:close-click-outside interaction) + :addBackgroundOverlay (:background-overlay interaction) + :animation (format-animation (:animation interaction))} + + :close-overlay + #js {:type "close-overlay" + :destination (when (:destination interaction) (shape-proxy plugin file-id page-id (:destination interaction))) + :animation (format-animation (:animation interaction))} + + :prev-screen + #js {:type "previous-screen"} + + :open-url + #js {:type "open-url" + :url (:url interaction)} + + nil)))) diff --git a/frontend/src/app/plugins/page.cljs b/frontend/src/app/plugins/page.cljs index 0ace08589..75df73d2a 100644 --- a/frontend/src/app/plugins/page.cljs +++ b/frontend/src/app/plugins/page.cljs @@ -8,11 +8,14 @@ "RPC for plugins runtime." (:require [app.common.colors :as cc] + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.record :as crc] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] + [app.main.data.workspace.interactions :as dwi] [app.main.store :as st] + [app.plugins.format :as format] [app.plugins.parser :as parser] [app.plugins.register :as r] [app.plugins.shape :as shape] @@ -20,6 +23,49 @@ [app.util.object :as obj] [cuerdas.core :as str])) +(deftype FlowProxy [$plugin $file $page $id] + Object + (remove [_] + (st/emit! (dwi/remove-flow $page $id)))) + +(defn flow-proxy? [p] + (instance? FlowProxy p)) + +(defn flow-proxy + [plugin-id file-id page-id id] + (crc/add-properties! + (FlowProxy. plugin-id file-id page-id id) + {:name "$plugin" :enumerable false :get (constantly plugin-id)} + {:name "$file" :enumerable false :get (constantly file-id)} + {:name "$page" :enumerable false :get (constantly page-id)} + {:name "$id" :enumerable false :get (constantly id)} + {:name "page" :enumerable false :get (fn [_] (u/locate-page file-id page-id))} + + {:name "name" + :get #(-> % u/proxy->flow :name) + :set + (fn [_ value] + (cond + (or (not (string? value)) (empty? value)) + (u/display-not-valid :name value) + + :else + (st/emit! (dwi/update-flow page-id id #(assoc % :name value)))))} + + {:name "startingFrame" + :get + (fn [self] + (let [frame (-> self u/proxy->flow :starting-frame)] + (u/locate-shape file-id page-id frame))) + :set + (fn [_ value] + (cond + (not (shape/shape-proxy? value)) + (u/display-not-valid :startingFrame value) + + :else + (st/emit! (dwi/update-flow page-id id #(assoc % :starting-frame (obj/get value "$id"))))))})) + (deftype PageProxy [$plugin $file $id] Object (getShapeById @@ -140,7 +186,30 @@ (u/display-not-valid :openPage "Plugin doesn't have 'content:read' permission") :else - (st/emit! (dw/go-to-page $id))))) + (st/emit! (dw/go-to-page $id)))) + + (createFlow + [_ name frame] + (cond + (or (not (string? name)) (empty? name)) + (u/display-not-valid :createFlow-name name) + + (not (shape/shape-proxy? frame)) + (u/display-not-valid :createFlow-frame frame) + + :else + (let [flow-id (uuid/next)] + (st/emit! (dwi/add-flow flow-id $id name (obj/get frame "$id"))) + (flow-proxy $plugin $file $id flow-id)))) + + (removeFlow + [_ flow] + (cond + (not (flow-proxy? flow)) + (u/display-not-valid :removeFlow-flow flow) + + :else + (st/emit! (dwi/remove-flow $id (obj/get flow "$id")))))) (crc/define-properties! PageProxy @@ -192,4 +261,10 @@ (u/display-not-valid :background "Plugin doesn't have 'content:write' permission") :else - (st/emit! (dw/change-canvas-color id {:color value}))))})) + (st/emit! (dw/change-canvas-color id {:color value}))))} + + {:name "flows" + :get + (fn [self] + (let [flows (d/nilv (-> (u/proxy->page self) :options :flows) [])] + (format/format-array #(flow-proxy plugin-id file-id id (:id %)) flows)))})) diff --git a/frontend/src/app/plugins/parser.cljs b/frontend/src/app/plugins/parser.cljs index 53092c569..fcb238617 100644 --- a/frontend/src/app/plugins/parser.cljs +++ b/frontend/src/app/plugins/parser.cljs @@ -23,6 +23,12 @@ [color] (if (string? color) (-> color str/lower) color)) +(defn parse-point + [^js point] + (when point + {:x (obj/get point "x") + :y (obj/get point "y")})) + ;; { ;; name?: string; ;; nameLike?: string; @@ -394,3 +400,164 @@ [^js content] (when (some? content) (into [] (map parse-command) content))) + +;; export interface PenpotDissolve { +;; type: 'dissolve'; +;; duration: number; +;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'; +;; } +;; +;; export interface PenpotSlide { +;; type: 'slide'; +;; way: 'in' | 'out'; +;; direction?: +;; | 'right' +;; | 'left' +;; | 'up' +;; | 'down'; +;; duration: number; +;; offsetEffect?: boolean; +;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'; +;; } +;; +;; export interface PenpotPush { +;; type: 'push'; +;; direction?: +;; | 'right' +;; | 'left' +;; | 'up' +;; | 'down'; +;; +;; duration: number; +;; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'; +;; } +;; +;; export type PenpotAnimation = PenpotDissolve | PenpotSlide | PenpotPush; + +(defn parse-animation + [^js animation] + (when animation + (let [animation-type (-> (obj/get animation "type") parse-keyword)] + (d/without-nils + (case animation-type + :dissolve + {:type animation-type + :duration (obj/get animation "duration") + :easing (-> (obj/get animation "easing") parse-keyword)} + + :slide + {:type animation-type + :way (-> (obj/get animation "way") parse-keyword) + :direction (-> (obj/get animation "direction") parse-keyword) + :duration (obj/get animation "duration") + :easing (-> (obj/get animation "easing") parse-keyword) + :offset-effect (obj/get animation "offsetEffect")} + + :push + {:type animation-type + :direction (-> (obj/get animation "direction") parse-keyword) + :duration (obj/get animation "duration") + :easing (-> (obj/get animation "easing") parse-keyword)} + + nil))))) + +;;export type PenpotAction = +;; | PenpotNavigateTo +;; | PenpotOpenOverlay +;; | PenpotToggleOverlay +;; | PenpotCloseOverlay +;; | PenpotPreviousScreen +;; | PenpotOpenUrl; +;; +;;export interface PenpotNavigateTo { +;; type: 'navigate-to'; +;; destination: PenpotFrame; +;; preserveScrollPosition?: boolean; +;; animation: PenpotAnimation; +;;} +;; +;;export interface PenpotOverlayAction { +;; destination: PenpotFrame; +;; relativeTo?: PenpotShape; +;; position?: +;; | 'manual' +;; | 'center' +;; | 'top-left' +;; | 'top-right' +;; | 'top-center' +;; | 'bottom-left' +;; | 'bottom-right' +;; | 'bottom-center'; +;; manualPositionLocation?: PenpotPoint; +;; closeWhenClickOutside?: boolean; +;; addBackgroundOverlay?: boolean; +;; animation: PenpotAnimation; +;;} +;; +;;export interface PenpotOpenOverlay extends PenpotOverlayAction { +;; type: 'open-overlay'; +;;} +;; +;;export interface PenpotToggleOverlay extends PenpotOverlayAction { +;; type: 'toggle-overlay'; +;;} +;; +;;export interface PenpotCloseOverlay { +;; type: 'close-overlay'; +;; destination?: PenpotFrame; +;; animation: PenpotAnimation; +;;} +;; +;;export interface PenpotPreviousScreen { +;; type: 'previous-screen'; +;;} +;; +;;export interface PenpotOpenUrl { +;; type: 'open-url'; +;; url: string; +;;} +(defn parse-action + [action] + (when action + (let [action-type (-> (obj/get action "type") parse-keyword)] + (d/without-nils + (case action-type + :navigate-to + {:action-type :navigate + :destination (-> (obj/get action "destination") (obj/get "$id")) + :preserve-scroll (obj/get action "preserveScrollPosition") + :animation (-> (obj/get action "animation") parse-animation)} + + (:open-overlay + :toggle-overlay) + {:action-type action-type + :destination (-> (obj/get action "destination") (obj/get "$id")) + :relative-to (-> (obj/get action "relativeTo") (obj/get "$id")) + :overlay-pos-type (-> (obj/get action "position") parse-keyword) + :overlay-position (-> (obj/get action "manualPositionLocation") parse-point) + :close-click-outside (obj/get action "closeWhenClickOutside") + :background-overlay (obj/get action "addBackgroundOverlay") + :animation (-> (obj/get action "animation") parse-animation)} + + :close-overlay + {:action-type action-type + :destination (-> (obj/get action "destination") (obj/get "$id")) + :animation (-> (obj/get action "animation") parse-animation)} + + :previous-screen + {:action-type :prev-screen} + + :open-url + {:action-type action-type + :url (obj/get action "url")} + + nil))))) + +(defn parse-interaction + [^js interaction] + (when interaction + (let [trigger (-> (obj/get interaction "trigger") parse-keyword) + delay (obj/get interaction "trigger") + action (-> (obj/get interaction "action") parse-action)] + (d/without-nils + (d/patch-object {:event-type trigger :delay delay} action))))) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index f8014b20b..1a8bb3e63 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -25,6 +25,7 @@ [app.common.types.shape :as cts] [app.common.types.shape.blur :as ctsb] [app.common.types.shape.export :as ctse] + [app.common.types.shape.interactions :as ctsi] [app.common.types.shape.layout :as ctl] [app.common.types.shape.path :as ctsp] [app.common.types.shape.radius :as ctsr] @@ -32,6 +33,7 @@ [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.data.workspace.groups :as dwg] + [app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shape-layout :as dwsl] @@ -52,6 +54,81 @@ [cuerdas.core :as str] [promesa.core :as p])) +(declare shape-proxy) +(declare shape-proxy?) + +(deftype InteractionProxy [$plugin $file $page $shape $index] + Object + (remove [_] + (st/emit! (dwi/remove-interaction {:id $shape} $index)))) + +(defn interaction-proxy? [p] + (instance? InteractionProxy p)) + +(defn interaction-proxy + [plugin-id file-id page-id shape-id index] + (crc/add-properties! + (InteractionProxy. plugin-id file-id page-id shape-id index) + {:name "$plugin" :enumerable false :get (constantly plugin-id)} + {:name "$file" :enumerable false :get (constantly file-id)} + {:name "$page" :enumerable false :get (constantly page-id)} + {:name "$shape" :enumerable false :get (constantly shape-id)} + {:name "$index" :enumerable false :get (constantly index)} + + ;; Not enumerable so we don't have an infinite loop + {:name "shape" :enumerable false + :get (fn [_] (shape-proxy plugin-id file-id page-id shape-id))} + + {:name "trigger" + :get #(-> % u/proxy->interaction :event-type format/format-key) + :set + (fn [_ value] + (let [value (parser/parse-keyword value)] + (cond + (not (contains? ctsi/event-types value)) + (u/display-not-valid :trigger value) + + :else + (st/emit! (dwi/update-interaction + {:id shape-id} + index + #(assoc % :event-type value) + {:page-id page-id})))))} + + {:name "delay" + :get #(-> % u/proxy->interaction :delay) + :set + (fn [_ value] + (cond + (or (not (number? value)) (not (pos? value))) + (u/display-not-valid :delay value) + + :else + (st/emit! (dwi/update-interaction + {:id shape-id} + index + #(assoc % :delay value) + {:page-id page-id}))))} + + {:name "action" + :get #(-> % u/proxy->interaction (format/format-action plugin-id file-id page-id)) + :set + (fn [self value] + (let [params (parser/parse-action value) + interaction + (-> (u/proxy->interaction self) + (d/patch-object params))] + (cond + (not (sm/validate ::ctsi/interaction interaction)) + (u/display-not-valid :action interaction) + + :else + (st/emit! (dwi/update-interaction + {:id shape-id} + index + #(d/patch-object % params) + {:page-id page-id})))))})) + (def lib-typography-proxy? nil) (def lib-component-proxy nil) @@ -62,8 +139,6 @@ (dwt/current-paragraph-values {:shape shape :attrs txt/paragraph-attrs}) (dwt/current-text-values {:shape shape :attrs txt/text-node-attrs}))) -(declare shape-proxy) -(declare shape-proxy?) (defn- shadow-defaults [shadow] @@ -446,6 +521,7 @@ [_] (st/emit! (dwl/detach-component $id))) + ;; Export (export [self value] (let [value (parser/parse-export value)] @@ -471,7 +547,31 @@ (rx/mapcat #(rp/cmd! :export {:cmd :get-resource :wait true :id (:id %) :blob? true})) (rx/mapcat #(.arrayBuffer %)) (rx/map #(js/Uint8Array. %)) - (rx/subs! resolve reject))))))))) + (rx/subs! resolve reject)))))))) + + ;; Interactions + (addInteraction + [self interaction] + (let [interaction + (-> ctsi/default-interaction + (d/patch-object (parser/parse-interaction interaction)))] + (cond + (not (sm/validate ::ctsi/interaction interaction)) + (u/display-not-valid :addInteraction interaction) + + :else + (let [index (-> (u/proxy->shape self) (:interactions []) count)] + (st/emit! (dwi/add-interaction $page $id interaction)) + (interaction-proxy $plugin $file $page $id index))))) + + (removeInteraction + [_ interaction] + (cond + (not (interaction-proxy? interaction)) + (u/display-not-valid :removeInteraction interaction) + + :else + (st/emit! (dwi/remove-interaction {:id $id} (obj/get interaction "$index")))))) (defn shape-proxy? [p] (instance? ShapeProxy p)) @@ -480,6 +580,8 @@ (do (set! flex/shape-proxy? shape-proxy?) (set! grid/shape-proxy? shape-proxy?)) +(set! format/shape-proxy shape-proxy) + (crc/define-properties! ShapeProxy {:name js/Symbol.toStringTag @@ -1036,7 +1138,17 @@ id (obj/get self "$id") objects (u/locate-objects file-id page-id)] (when (ctl/grid-layout-immediate-child-id? objects id) - (grid/layout-cell-proxy plugin-id file-id page-id id))))}) + (grid/layout-cell-proxy plugin-id file-id page-id id))))} + + + ;; Interactions + {:name "interactions" + :get + (fn [self] + (let [interactions (-> self u/proxy->shape :interactions)] + (format/format-array + #(interaction-proxy plugin-id file-id page-id id %) + (range 0 (count interactions)))))}) (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)) (crc/add-properties! diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 4df6207d8..0e143834e 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -113,6 +113,25 @@ (when (and (some? file-id) (some? id)) (locate-library-component file-id id)))) +(defn proxy->flow + [proxy] + (let [file-id (obj/get proxy "$file") + page-id (obj/get proxy "$page") + flow-id (obj/get proxy "$id") + page (locate-page file-id page-id)] + (when (some? page) + (d/seek #(= (:id %) flow-id) (-> page :options :flows))))) + +(defn proxy->interaction + [proxy] + (let [file-id (obj/get proxy "$file") + page-id (obj/get proxy "$page") + shape-id (obj/get proxy "$shape") + index (obj/get proxy "$index") + shape (locate-shape file-id page-id shape-id)] + (when (some? shape) + (get-in shape [:interactions index])))) + (defn get-data ([self attr] (-> (obj/get self "_data") diff --git a/frontend/src/app/util/path/format.cljs b/frontend/src/app/util/path/format.cljs index b120ff4d3..5ec19173a 100644 --- a/frontend/src/app/util/path/format.cljs +++ b/frontend/src/app/util/path/format.cljs @@ -134,3 +134,4 @@ (catch :default err (.error js/console err) nil))) +