🎉 Add basic interaction to shapes

This commit is contained in:
Andrés Moya 2020-04-27 14:31:57 +02:00
parent 49023117c3
commit 1e898f94f3
32 changed files with 1024 additions and 226 deletions

View file

@ -64,19 +64,23 @@
(persistent! (persistent!
(reduce #(assoc! %1 (getter %2) %2) (transient {}) coll))) (reduce #(assoc! %1 (getter %2) %2) (transient {}) coll)))
(defn index-of (defn index-of-pred
[coll v] [coll pred]
(loop [c (first coll) (loop [c (first coll)
coll (rest coll) coll (rest coll)
index 0] index 0]
(if (nil? c) (if (nil? c)
nil nil
(if (= c v) (if (pred c)
index index
(recur (first coll) (recur (first coll)
(rest coll) (rest coll)
(inc index)))))) (inc index))))))
(defn index-of
[coll v]
(index-of-pred coll #(= % v)))
(defn remove-nil-vals (defn remove-nil-vals
"Given a map, return a map removing key-value "Given a map, return a map removing key-value
pairs when value is `nil`." pairs when value is `nil`."

View file

@ -135,6 +135,19 @@
::grid-x ::grid-x
::grid-color])) ::grid-color]))
;; Interactions
(s/def ::event-type #{:click}) ; In the future we will have more options
(s/def ::action-type #{:navigate})
(s/def ::destination uuid?)
(s/def ::interaction
(s/keys :req-un [::event-type
::action-type
::destination]))
(s/def ::interactions (s/coll-of ::interaction :kind vector?))
;; Page Data related ;; Page Data related
(s/def ::blocked boolean?) (s/def ::blocked boolean?)
(s/def ::collapsed boolean?) (s/def ::collapsed boolean?)
@ -194,7 +207,8 @@
::stroke-width ::stroke-width
::stroke-alignment ::stroke-alignment
::text-align ::text-align
::width ::height])) ::width ::height
::interactions]))
(s/def ::minimal-shape (s/def ::minimal-shape
(s/keys :req-un [::type ::name] (s/keys :req-un [::type ::name]

View file

@ -26,12 +26,19 @@ You can deactivate debug mode with
(-debug! <option>) ; to disable only one (-debug! <option>) ; to disable only one
``` ```
There are also some useful functions: ## Debug state and objects
There are also some useful functions to visualize the global state or any
complex object:
```clojure ```clojure
(dump-state) ; to print in console all the global state (ns uxbox.util.debug)
(dump-objects) ; to print in console all objects in workspace
(logjs <msg> <var>) ; to print the value of a variable (logjs <msg> <var>) ; to print the value of a variable
(tap <fn>) ; to include a function with side effect (e.g. logjs) in a transducer. (tap <fn>) ; to include a function with side effect (e.g. logjs) in a transducer.
(ns uxbox.main.store)
(dump-state) ; to print in console all the global state
(dump-objects) ; to print in console all objects in workspace
``` ```

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 500 500"><defs/><rect width="484.4" height="484.4" x="-492" y="-492" stroke="#64666a" stroke-width="15.6" rx="242.2" transform="scale(-1)"/><path fill="#64666a" fill-rule="evenodd" d="M343 250c0 12-9 21-20 21H145c-11 0-20-9-20-21s9-21 20-21h178c11 0 20 9 20 21z" clip-rule="evenodd"/><path fill="#64666a" fill-rule="evenodd" d="M309 269c10-11 10-27 0-38l-68-70c-8-8-8-22 0-30s21-8 29 0l97 100c11 11 11 27 0 38l-97 100c-8 8-21 8-29 0s-8-22 0-30z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 539 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"><path d="M436.58 250c0 23.2-18.35 42.009-40.991 42.009h-354.6C18.353 292.009 0 273.2 0 250c0-23.2 18.352-42.008 40.99-42.008h354.599c22.64 0 40.99 18.808 40.99 42.008z"/><path d="M368.395 287.862c20.447-20.893 20.447-54.83 0-75.724l-137.27-140.28c-16.087-16.438-16.087-43.14 0-59.579 16.02-16.372 41.946-16.372 57.967 0l195.575 199.86c20.444 20.892 20.444 54.83 0 75.723l-195.575 199.86c-16.02 16.371-41.947 16.371-57.968 0-16.086-16.438-16.086-43.141 0-59.579z"/></svg>

After

Width:  |  Height:  |  Size: 536 B

View file

@ -1,5 +1,4 @@
{ {
"dashboard.grid.delete" : { "dashboard.grid.delete" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:61", "src/uxbox/main/ui/dashboard/grid.cljs:92" ], "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:61", "src/uxbox/main/ui/dashboard/grid.cljs:92" ],
"translations" : { "translations" : {
@ -259,7 +258,7 @@
} }
}, },
"ds.search.placeholder" : { "ds.search.placeholder" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:174" ], "used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:168" ],
"translations" : { "translations" : {
"en" : "Search...", "en" : "Search...",
"fr" : "Rechercher..." "fr" : "Rechercher..."
@ -329,28 +328,28 @@
} }
}, },
"errors.auth.unauthorized" : { "errors.auth.unauthorized" : {
"used-in" : [ "src/uxbox/main/data/auth.cljs:56" ], "used-in" : [ "src/uxbox/main/data/auth.cljs:57" ],
"translations" : { "translations" : {
"en" : "Username or password seems to be wrong.", "en" : "Username or password seems to be wrong.",
"fr" : "Le nom d'utilisateur ou le mot de passe semble être faux." "fr" : "Le nom d'utilisateur ou le mot de passe semble être faux."
} }
}, },
"errors.generic" : { "errors.generic" : {
"used-in" : [ "src/uxbox/main/ui.cljs:175" ], "used-in" : [ "src/uxbox/main/ui.cljs:179" ],
"translations" : { "translations" : {
"en" : "Something wrong has happened.", "en" : "Something wrong has happened.",
"fr" : "Quelque chose c'est mal passé." "fr" : "Quelque chose c'est mal passé."
} }
}, },
"errors.network" : { "errors.network" : {
"used-in" : [ "src/uxbox/main/ui.cljs:169" ], "used-in" : [ "src/uxbox/main/ui.cljs:173" ],
"translations" : { "translations" : {
"en" : "Unable to connect to backend server.", "en" : "Unable to connect to backend server.",
"fr" : "Impossible de se connecter au serveur principal." "fr" : "Impossible de se connecter au serveur principal."
} }
}, },
"header.sitemap" : { "header.sitemap" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:99" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:74" ],
"translations" : { "translations" : {
"en" : null, "en" : null,
"fr" : null "fr" : null
@ -580,7 +579,7 @@
} }
}, },
"settings.password" : { "settings.password" : {
"used-in" : [ "src/uxbox/main/ui/settings/header.cljs:34" ], "used-in" : [ "src/uxbox/main/ui/settings/header.cljs:36" ],
"translations" : { "translations" : {
"en" : "PASSWORD", "en" : "PASSWORD",
"fr" : "MOT DE PASSE" "fr" : "MOT DE PASSE"
@ -622,14 +621,14 @@
} }
}, },
"settings.profile" : { "settings.profile" : {
"used-in" : [ "src/uxbox/main/ui/settings/header.cljs:30" ], "used-in" : [ "src/uxbox/main/ui/settings/header.cljs:32" ],
"translations" : { "translations" : {
"en" : "PROFILE", "en" : "PROFILE",
"fr" : "PROFIL" "fr" : "PROFIL"
} }
}, },
"settings.profile.lang" : { "settings.profile.lang" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:89" ], "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:88" ],
"translations" : { "translations" : {
"en" : "Default language", "en" : "Default language",
"fr" : "Langue par défaut" "fr" : "Langue par défaut"
@ -643,109 +642,127 @@
} }
}, },
"settings.profile.section-basic-data" : { "settings.profile.section-basic-data" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:63" ], "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:62" ],
"translations" : { "translations" : {
"en" : "Name, username and email", "en" : "Name, username and email",
"fr" : "Nom, nom d'utilisateur et adresse email" "fr" : "Nom, nom d'utilisateur et adresse email"
} }
}, },
"settings.profile.section-theme-data" : { "settings.profile.section-theme-data" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:98" ], "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:97" ],
"translations" : { "translations" : {
"en" : "Default theme", "en" : "Default theme",
"fr" : "Thème par défaut" "fr" : "Thème par défaut"
} }
}, },
"settings.profile.your-avatar" : { "settings.profile.your-avatar" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:143" ], "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:142" ],
"translations" : { "translations" : {
"en" : "Your avatar", "en" : "Your avatar",
"fr" : "Votre avatar" "fr" : "Votre avatar"
} }
}, },
"settings.profile.your-email" : { "settings.profile.your-email" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:84" ], "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:83" ],
"translations" : { "translations" : {
"en" : null, "en" : null,
"fr" : null "fr" : null
} }
}, },
"settings.profile.your-name" : { "settings.profile.your-name" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:72" ], "used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:71" ],
"translations" : { "translations" : {
"en" : "Your name", "en" : "Your name",
"fr" : "Votre nom complet" "fr" : "Votre nom complet"
} }
}, },
"settings.update-settings" : { "settings.update-settings" : {
"used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:42", "src/uxbox/main/ui/settings/password.cljs:102", "src/uxbox/main/ui/settings/profile.cljs:110" ], "used-in" : [ "src/uxbox/main/ui/settings/notifications.cljs:42", "src/uxbox/main/ui/settings/password.cljs:102", "src/uxbox/main/ui/settings/profile.cljs:109" ],
"translations" : { "translations" : {
"en" : "Update settings", "en" : "Update settings",
"fr" : "Mettre à jour les paramètres" "fr" : "Mettre à jour les paramètres"
} }
}, },
"viewer.empty-state" : { "viewer.empty-state" : {
"used-in" : [ "src/uxbox/main/ui/viewer.cljs:43" ], "used-in" : [ "src/uxbox/main/ui/viewer.cljs:44" ],
"translations" : { "translations" : {
"en" : "No frames found on the page." "en" : "No frames found on the page."
} }
}, },
"viewer.frame-not-found" : { "viewer.frame-not-found" : {
"used-in" : [ "src/uxbox/main/ui/viewer.cljs:47" ], "used-in" : [ "src/uxbox/main/ui/viewer.cljs:48" ],
"translations" : { "translations" : {
"en" : "Frame not found." "en" : "Frame not found."
} }
}, },
"viewer.header.dont-show-interactions" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:40" ],
"translations" : {
"en" : "Don't show interactions"
}
},
"viewer.header.edit-page" : { "viewer.header.edit-page" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:112" ], "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:137" ],
"translations" : { "translations" : {
"en" : "Edit page" "en" : "Edit page"
} }
}, },
"viewer.header.fullscreen" : { "viewer.header.fullscreen" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:123" ], "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:148" ],
"translations" : { "translations" : {
"en" : "Full Screen" "en" : "Full Screen"
} }
}, },
"viewer.header.share.copy-link" : { "viewer.header.share.copy-link" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:64" ], "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:86" ],
"translations" : { "translations" : {
"en" : "Copy link" "en" : "Copy link"
} }
}, },
"viewer.header.share.create-link" : { "viewer.header.share.create-link" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:72" ], "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:94" ],
"translations" : { "translations" : {
"en" : "Create link" "en" : "Create link"
} }
}, },
"viewer.header.share.placeholder" : { "viewer.header.share.placeholder" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:62" ], "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:84" ],
"translations" : { "translations" : {
"en" : "Share link will apear here" "en" : "Share link will apear here"
} }
}, },
"viewer.header.share.remove-link" : { "viewer.header.share.remove-link" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:70" ], "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:92" ],
"translations" : { "translations" : {
"en" : "Remove link" "en" : "Remove link"
} }
}, },
"viewer.header.share.subtitle" : { "viewer.header.share.subtitle" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:66" ], "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:88" ],
"translations" : { "translations" : {
"en" : "Anyone with the link will have access" "en" : "Anyone with the link will have access"
} }
}, },
"viewer.header.share.title" : { "viewer.header.share.title" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:50", "src/uxbox/main/ui/viewer/header.cljs:52", "src/uxbox/main/ui/viewer/header.cljs:58" ], "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:72", "src/uxbox/main/ui/viewer/header.cljs:74", "src/uxbox/main/ui/viewer/header.cljs:80" ],
"translations" : { "translations" : {
"en" : "Share link" "en" : "Share link"
} }
}, },
"viewer.header.show-interactions" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:44" ],
"translations" : {
"en" : "Show interactions"
}
},
"viewer.header.show-interactions-on-click" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:48" ],
"translations" : {
"en" : "Show interactions on click"
}
},
"viewer.header.sitemap" : { "viewer.header.sitemap" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:97" ], "used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:121" ],
"translations" : { "translations" : {
"en" : "Sitemap" "en" : "Sitemap"
} }
@ -799,117 +816,123 @@
} }
}, },
"workspace.header.menu.hide-grid" : { "workspace.header.menu.hide-grid" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:119" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:94" ],
"translations" : { "translations" : {
"en" : "Hide grid" "en" : "Hide grid"
} }
}, },
"workspace.header.menu.hide-layers" : { "workspace.header.menu.hide-layers" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:126" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:101" ],
"translations" : { "translations" : {
"en" : "Hide layers" "en" : "Hide layers"
} }
}, },
"workspace.header.menu.hide-libraries" : { "workspace.header.menu.hide-libraries" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:140" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:115" ],
"translations" : { "translations" : {
"en" : "Hide libraries" "en" : "Hide libraries"
} }
}, },
"workspace.header.menu.hide-palette" : { "workspace.header.menu.hide-palette" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:133" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:108" ],
"translations" : { "translations" : {
"en" : "Hide color palette" "en" : "Hide color palette"
} }
}, },
"workspace.header.menu.hide-rules" : { "workspace.header.menu.hide-rules" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:112" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:87" ],
"translations" : { "translations" : {
"en" : "Hide rules" "en" : "Hide rules"
} }
}, },
"workspace.header.menu.show-grid" : { "workspace.header.menu.show-grid" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:120" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:95" ],
"translations" : { "translations" : {
"en" : "Show grid" "en" : "Show grid"
} }
}, },
"workspace.header.menu.show-layers" : { "workspace.header.menu.show-layers" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:127" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:102" ],
"translations" : { "translations" : {
"en" : "Show layers" "en" : "Show layers"
} }
}, },
"workspace.header.menu.show-libraries" : { "workspace.header.menu.show-libraries" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:141" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:116" ],
"translations" : { "translations" : {
"en" : "Show libraries" "en" : "Show libraries"
} }
}, },
"workspace.header.menu.show-palette" : { "workspace.header.menu.show-palette" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:134" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:109" ],
"translations" : { "translations" : {
"en" : "Show color palette" "en" : "Show color palette"
} }
}, },
"workspace.header.menu.show-rules" : { "workspace.header.menu.show-rules" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:113" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:88" ],
"translations" : { "translations" : {
"en" : "Show rules" "en" : "Show rules"
} }
}, },
"workspace.header.viewer" : { "workspace.header.viewer" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:176" ], "used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:153" ],
"translations" : { "translations" : {
"en" : "View mode (Ctrl + P)", "en" : "View mode (Ctrl + P)",
"fr" : "Mode visualisation (Ctrl + P)" "fr" : "Mode visualisation (Ctrl + P)"
} }
}, },
"workspace.library.all" : { "workspace.library.all" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:122" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:121" ],
"translations" : { "translations" : {
"en" : "All libraries" "en" : "All libraries"
} }
}, },
"workspace.library.icons" : { "workspace.library.icons" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:172" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:171" ],
"translations" : { "translations" : {
"en" : "Icons" "en" : "Icons"
} }
}, },
"workspace.library.images" : { "workspace.library.images" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:177" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:176" ],
"translations" : { "translations" : {
"en" : "Images" "en" : "Images"
} }
}, },
"workspace.library.libraries" : { "workspace.library.libraries" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:154" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:153" ],
"translations" : { "translations" : {
"en" : "Libraries" "en" : "Libraries"
} }
}, },
"workspace.library.own" : { "workspace.library.own" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:123" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:122" ],
"translations" : { "translations" : {
"en" : "My libraries" "en" : "My libraries"
} }
}, },
"workspace.library.store" : { "workspace.library.store" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:124" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:123" ],
"translations" : { "translations" : {
"en" : "Store libraries" "en" : "Store libraries"
} }
}, },
"workspace.options.color" : { "workspace.options.color" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:88" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:89" ],
"translations" : { "translations" : {
"en" : "Color", "en" : "Color",
"fr" : "Couleur" "fr" : "Couleur"
} }
}, },
"workspace.options.design" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:76" ],
"translations" : {
"en" : "Design"
}
},
"workspace.options.fill" : { "workspace.options.fill" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:51" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:69", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:446" ],
"translations" : { "translations" : {
"en" : "Fill", "en" : "Fill",
"fr" : "Fond" "fr" : "Fond"
@ -922,33 +945,121 @@
}, },
"unused" : true "unused" : true
}, },
"workspace.options.font-options.decoration": "Decoration",
"workspace.options.font-options.none": "None",
"workspace.options.font-options.underline": "Underline",
"workspace.options.font-options.strikethrough": "Strikethrough",
"workspace.options.font-options.align-left": "Align left",
"workspace.options.font-options.align-center": "Align center",
"workspace.options.font-options.align-right": "Align right",
"workspace.options.font-options.align-justify": "Justify",
"workspace.options.font-options.line-height": "Line height",
"workspace.options.font-options.letter-spacing": "Letter Spacing",
"workspace.options.font-options.vertical-align": "Vertical align",
"workspace.options.font-options.align-top": "Align top",
"workspace.options.font-options.align-middle": "Align middle",
"workspace.options.font-options.align-bottom": "Align bottom",
"workspace.options.font-options.text-case": "Case",
"workspace.options.font-options.uppercase": "Uppercase",
"workspace.options.font-options.lowercase": "Lowercase",
"workspace.options.font-options.titlecase": "Titlecase",
"workspace.options.font-options" : { "workspace.options.font-options" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:85" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:452" ],
"translations" : { "translations" : {
"en" : "Text", "en" : "Text",
"fr" : "TODO" "fr" : "TODO"
} }
}, },
"workspace.options.font-options.align-bottom" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:357" ],
"translations" : {
"en" : "Align bottom"
}
},
"workspace.options.font-options.align-center" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:171" ],
"translations" : {
"en" : "Align center"
}
},
"workspace.options.font-options.align-justify" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:181" ],
"translations" : {
"en" : "Justify"
}
},
"workspace.options.font-options.align-left" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:166" ],
"translations" : {
"en" : "Align left"
}
},
"workspace.options.font-options.align-middle" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:352" ],
"translations" : {
"en" : "Align middle"
}
},
"workspace.options.font-options.align-right" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:176" ],
"translations" : {
"en" : "Align right"
}
},
"workspace.options.font-options.align-top" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:347" ],
"translations" : {
"en" : "Align top"
}
},
"workspace.options.font-options.decoration" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:378" ],
"translations" : {
"en" : "Decoration"
}
},
"workspace.options.font-options.letter-spacing" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:304" ],
"translations" : {
"en" : "Letter Spacing"
}
},
"workspace.options.font-options.line-height" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:292" ],
"translations" : {
"en" : "Line height"
}
},
"workspace.options.font-options.lowercase" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:427" ],
"translations" : {
"en" : "Lowercase"
}
},
"workspace.options.font-options.none" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:381", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:417" ],
"translations" : {
"en" : "None"
}
},
"workspace.options.font-options.strikethrough" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:393" ],
"translations" : {
"en" : "Strikethrough"
}
},
"workspace.options.font-options.text-case" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:414" ],
"translations" : {
"en" : "Case"
}
},
"workspace.options.font-options.titlecase" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:432" ],
"translations" : {
"en" : "Titlecase"
}
},
"workspace.options.font-options.underline" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:387" ],
"translations" : {
"en" : "Underline"
}
},
"workspace.options.font-options.uppercase" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:422" ],
"translations" : {
"en" : "Uppercase"
}
},
"workspace.options.font-options.vertical-align" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:344" ],
"translations" : {
"en" : "Vertical align"
}
},
"workspace.options.font-weight" : { "workspace.options.font-weight" : {
"translations" : { "translations" : {
"en" : "Font Size & Weight", "en" : "Font Size & Weight",
@ -957,7 +1068,7 @@
"unused" : true "unused" : true
}, },
"workspace.options.grid-options" : { "workspace.options.grid-options" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:75" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:76" ],
"translations" : { "translations" : {
"en" : "Grid settings", "en" : "Grid settings",
"fr" : "Paramètres de la grille" "fr" : "Paramètres de la grille"
@ -977,6 +1088,18 @@
}, },
"unused" : true "unused" : true
}, },
"workspace.options.navigate-to" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:51" ],
"translations" : {
"en" : "Navigate to"
}
},
"workspace.options.none" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:64" ],
"translations" : {
"en" : "None"
}
},
"workspace.options.opacity" : { "workspace.options.opacity" : {
"translations" : { "translations" : {
"en" : "Opacity", "en" : "Opacity",
@ -985,21 +1108,27 @@
"unused" : true "unused" : true
}, },
"workspace.options.position" : { "workspace.options.position" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:144", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:126" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:135", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:126" ],
"translations" : { "translations" : {
"en" : "Position", "en" : "Position",
"fr" : "Position" "fr" : "Position"
} }
}, },
"workspace.options.prototype" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:85" ],
"translations" : {
"en" : "Prototype"
}
},
"workspace.options.radius" : { "workspace.options.radius" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:188" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:183" ],
"translations" : { "translations" : {
"en" : "Radius", "en" : "Radius",
"fr" : "TODO" "fr" : "TODO"
} }
}, },
"workspace.options.rotation" : { "workspace.options.rotation" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:164" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:159" ],
"translations" : { "translations" : {
"en" : "Rotation", "en" : "Rotation",
"fr" : "TODO" "fr" : "TODO"
@ -1012,67 +1141,79 @@
}, },
"unused" : true "unused" : true
}, },
"workspace.options.select-a-shape" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:45" ],
"translations" : {
"en" : "Select a shape, artboard or group to drag a connection to other artboard."
}
},
"workspace.options.select-artboard" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:57" ],
"translations" : {
"en" : "Select artboard"
}
},
"workspace.options.size" : { "workspace.options.size" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:78", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:92", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:120", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:101" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:79", "src/uxbox/main/ui/workspace/sidebar/options/measures.cljs:107", "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:101" ],
"translations" : { "translations" : {
"en" : "Size", "en" : "Size",
"fr" : "Taille" "fr" : "Taille"
} }
}, },
"workspace.options.size-presets" : { "workspace.options.size-presets" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:81" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/frame.cljs:83" ],
"translations" : { "translations" : {
"en" : "Size presets" "en" : "Size presets"
} }
}, },
"workspace.options.stroke" : { "workspace.options.stroke" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:81", "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:142" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:109", "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:173" ],
"translations" : { "translations" : {
"en" : "Stroke", "en" : "Stroke",
"fr" : null "fr" : null
} }
}, },
"workspace.options.stroke.center" : { "workspace.options.stroke.center" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:128" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:159" ],
"translations" : { "translations" : {
"en" : "Center" "en" : "Center"
} }
}, },
"workspace.options.stroke.dashed" : { "workspace.options.stroke.dashed" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:136" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:167" ],
"translations" : { "translations" : {
"en" : "Dashed", "en" : "Dashed",
"fr" : "Tiré" "fr" : "Tiré"
} }
}, },
"workspace.options.stroke.dotted" : { "workspace.options.stroke.dotted" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:135" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:166" ],
"translations" : { "translations" : {
"en" : "Dotted", "en" : "Dotted",
"fr" : "Pointillé" "fr" : "Pointillé"
} }
}, },
"workspace.options.stroke.inner" : { "workspace.options.stroke.inner" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:129" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:160" ],
"translations" : { "translations" : {
"en" : "Inside" "en" : "Inside"
} }
}, },
"workspace.options.stroke.mixed" : { "workspace.options.stroke.mixed" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:137" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:168" ],
"translations" : { "translations" : {
"en" : "Mixed", "en" : "Mixed",
"fr" : "Mixte" "fr" : "Mixte"
} }
}, },
"workspace.options.stroke.outer" : { "workspace.options.stroke.outer" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:130" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:161" ],
"translations" : { "translations" : {
"en" : "Outside" "en" : "Outside"
} }
}, },
"workspace.options.stroke.solid" : { "workspace.options.stroke.solid" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:134" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:165" ],
"translations" : { "translations" : {
"en" : "Solid", "en" : "Solid",
"fr" : "Solide" "fr" : "Solide"
@ -1092,6 +1233,12 @@
}, },
"unused" : true "unused" : true
}, },
"workspace.options.use-play-button" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs:47" ],
"translations" : {
"en" : "Use the play button at the header to run the prototype view."
}
},
"workspace.sidebar.icons" : { "workspace.sidebar.icons" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/icons.cljs:88" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/icons.cljs:88" ],
"translations" : { "translations" : {
@ -1107,7 +1254,7 @@
"unused" : true "unused" : true
}, },
"workspace.sidebar.sitemap" : { "workspace.sidebar.sitemap" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/sitemap.cljs:130" ], "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/sitemap.cljs:140" ],
"translations" : { "translations" : {
"en" : "Pages", "en" : "Pages",
"fr" : "Pages" "fr" : "Pages"
@ -1211,7 +1358,7 @@
} }
}, },
"workspace.viewport.click-to-close-path" : { "workspace.viewport.click-to-close-path" : {
"used-in" : [ "src/uxbox/main/ui/workspace/drawarea.cljs:335" ], "used-in" : [ "src/uxbox/main/ui/workspace/drawarea.cljs:360" ],
"translations" : { "translations" : {
"en" : "Click to close the path" "en" : "Click to close the path"
} }

View file

@ -52,6 +52,7 @@
@import 'main/partials/sidebar-align-options'; @import 'main/partials/sidebar-align-options';
@import 'main/partials/sidebar-element-options'; @import 'main/partials/sidebar-element-options';
@import 'main/partials/sidebar-icons'; @import 'main/partials/sidebar-icons';
@import 'main/partials/sidebar-interactions';
@import 'main/partials/sidebar-layers'; @import 'main/partials/sidebar-layers';
@import 'main/partials/sidebar-sitemap'; @import 'main/partials/sidebar-sitemap';
@import 'main/partials/sidebar-document-history'; @import 'main/partials/sidebar-document-history';

View file

@ -7,12 +7,9 @@
.element-options { .element-options {
display: flex; display: flex;
flex-direction: column;
width: 100%; width: 100%;
> div {
width: 100%;
}
.element-icons { .element-icons {
background-color: $color-gray-60; background-color: $color-gray-60;
border: 1px solid $color-gray-60; border: 1px solid $color-gray-60;
@ -307,6 +304,10 @@
margin-left: auto; margin-left: auto;
} }
&.dropdown-separator:not(:last-child) {
border-bottom: 1px solid $color-gray-10;
}
&.dropdown-label:not(:first-child) { &.dropdown-label:not(:first-child) {
border-top: 1px solid $color-gray-10; border-top: 1px solid $color-gray-10;
} }
@ -488,6 +489,29 @@
} }
} }
.navigate-icon {
background-color: $color-gray-60;
cursor: pointer;
margin-left: $small;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
svg {
height: 16px;
fill: $color-gray-30;
width: 16px;
}
&:hover {
svg {
stroke: $color-gray-10;
}
}
}
.input-icon { .input-icon {
align-items: center; align-items: center;
display: flex; display: flex;

View file

@ -4,3 +4,21 @@
// //
// Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz> // Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
// Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> // Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
.interactions-help {
font-size: $fs12;
margin: 0 $medium;
text-align: center;
}
.interactions-help-icon {
height: 32px;
width: 32px;
margin: $medium auto;
svg {
fill: $color-gray-40;
height: 32px;
width: 32px;
}
}

View file

@ -111,11 +111,13 @@
} }
} }
} }
.tool-window-content { .tool-window-content {
display: flex; display: flex;
flex-wrap: wrap; flex-direction: column;
overflow-y: auto; overflow-y: auto;
height: 100%; height: 100%;
width: 100%;
} }
.element-list { .element-list {

View file

@ -42,6 +42,30 @@
} }
} }
.header-icon {
align-items: center;
cursor: pointer;
display: flex;
justify-content: center;
a {
height: 16px;
width: 16px;
svg {
fill: $color-gray-30;
height: 16px;
width: 16px;
}
&:hover {
svg {
fill: $color-primary;
}
}
}
}
.sitemap-zone { .sitemap-zone {
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
@ -88,7 +112,7 @@
.options-zone { .options-zone {
align-items: center; align-items: center;
display: flex; display: flex;
width: 300px; width: 350px;
justify-content: flex-end; justify-content: flex-end;
position: relative; position: relative;
@ -223,6 +247,40 @@
} }
.custom-select-dropdown {
position: absolute;
left: 0;
z-index: 12;
width: 200px;
max-height: 30rem;
min-width: 7rem;
overflow-y: auto;
background-color: $color-white;
border-radius: $br-small;
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25);
li {
color: $color-gray-60;
cursor: pointer;
font-size: $fs14;
display: flex;
padding: $small $small $small 25px;
&.selected {
background-image: url(/images/icons/tick.svg);
background-repeat: no-repeat;
background-position: 5% 48%;
background-size: 10px;
font-weight: bold;
}
&:hover {
background-color: $color-primary-lighter;
}
}
}
.zoom-dropdown { .zoom-dropdown {
left : 150px; left : 150px;
top: 45px; top: 45px;

View file

@ -53,6 +53,7 @@
(def icon-empty (icon-xref :icon-empty)) (def icon-empty (icon-xref :icon-empty))
(def image (icon-xref :image)) (def image (icon-xref :image))
(def infocard (icon-xref :infocard)) (def infocard (icon-xref :infocard))
(def interaction (icon-xref :interaction))
(def layers (icon-xref :layers)) (def layers (icon-xref :layers))
(def letter-spacing (icon-xref :letter-spacing)) (def letter-spacing (icon-xref :letter-spacing))
(def line (icon-xref :line)) (def line (icon-xref :line))
@ -66,6 +67,7 @@
(def mail (icon-xref :mail)) (def mail (icon-xref :mail))
(def minus (icon-xref :minus)) (def minus (icon-xref :minus))
(def move (icon-xref :move)) (def move (icon-xref :move))
(def navigate (icon-xref :navigate))
(def options (icon-xref :options)) (def options (icon-xref :options))
(def organize (icon-xref :organize)) (def organize (icon-xref :organize))
(def palette (icon-xref :palette)) (def palette (icon-xref :palette))

View file

@ -31,6 +31,8 @@
(s/def ::file (s/keys :req-un [::id ::name])) (s/def ::file (s/keys :req-un [::id ::name]))
(s/def ::page (s/keys :req-un [::id ::name ::cp/data])) (s/def ::page (s/keys :req-un [::id ::name ::cp/data]))
(s/def ::interactions-mode #{:hide :show :show-on-click})
(s/def ::bundle (s/def ::bundle
(s/keys :req-un [::project ::file ::page])) (s/keys :req-un [::project ::file ::page]))
@ -45,7 +47,10 @@
(ptk/reify ::initialize (ptk/reify ::initialize
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc state :viewer-local {:zoom 1 :page-id page-id})) (assoc state :viewer-local {:zoom 1
:page-id page-id
:interactions-mode :hide
:show-interactions? false}))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -178,6 +183,52 @@
(when (< index (dec total)) (when (< index (dec total))
(rx/of (rt/nav :viewer pparams (assoc qparams :index (inc index))))))))) (rx/of (rt/nav :viewer pparams (assoc qparams :index (inc index)))))))))
(defn set-interactions-mode
[mode]
(us/verify ::interactions-mode mode)
(ptk/reify ::set-interactions-mode
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:viewer-local :interactions-mode] mode)
(assoc-in [:viewer-local :show-interactions?] (case mode
:hide false
:show true
:show-on-click false))))))
(declare flash-done)
(def flash-interactions
(ptk/reify ::flash-interactions
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:viewer-local :show-interactions?] true))
ptk/WatchEvent
(watch [_ state stream]
(let [stopper (rx/filter (ptk/type? ::flash-interactions) stream)]
(->> (rx/of flash-done)
(rx/delay 1000)
(rx/take-until stopper))))))
(def flash-done
(ptk/reify ::flash-done
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:viewer-local :show-interactions?] false))))
;; --- Navigation
(defn go-to-frame
[frame-id]
(us/verify ::us/uuid frame-id)
(ptk/reify ::go-to-frame
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get-in state [:viewer-local :page-id])
frames (get-in state [:viewer-data :frames])
index (d/index-of-pred frames #(= (:id %) frame-id))]
(rx/of (rt/nav :viewer {:page-id page-id} {:index index}))))))
;; --- Shortcuts ;; --- Shortcuts

View file

@ -68,13 +68,16 @@
:element-options :element-options
:rules}) :rules})
(s/def ::options-mode #{:design :prototype})
(def workspace-default (def workspace-default
{:zoom 1 {:zoom 1
:flags #{} :flags #{}
:selected #{} :selected #{}
:drawing nil :drawing nil
:drawing-tool nil :drawing-tool nil
:tooltip nil}) :tooltip nil
:options-mode :design})
(def initialize-layout (def initialize-layout
(ptk/reify ::initialize-layout (ptk/reify ::initialize-layout
@ -243,6 +246,16 @@
(conj flags flag)))))] (conj flags flag)))))]
(reduce reduce-fn state flags))))) (reduce reduce-fn state flags)))))
;; --- Set element options mode
(defn set-options-mode
[mode]
(us/assert ::options-mode mode)
(ptk/reify ::set-options-mode
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :options-mode] mode))))
;; --- Tooltip ;; --- Tooltip
(defn assign-cursor-tooltip (defn assign-cursor-tooltip

View file

@ -9,6 +9,7 @@
(:require (:require
[rumext.alpha :as mf] [rumext.alpha :as mf]
[uxbox.common.uuid :as uuid] [uxbox.common.uuid :as uuid]
[uxbox.common.pages :as cp]
[uxbox.util.math :as mth] [uxbox.util.math :as mth]
[uxbox.util.geom.shapes :as geom] [uxbox.util.geom.shapes :as geom]
[uxbox.util.geom.point :as gpt] [uxbox.util.geom.point :as gpt]
@ -110,3 +111,34 @@
[:& shape-wrapper {:shape item [:& shape-wrapper {:shape item
:key (:id item)}]))])) :key (:id item)}]))]))
(mf/defc frame-svg
{::mf/wrap [mf/memo]}
[{:keys [objects frame zoom] :or {zoom 1} :as props}]
(let [modifier (-> (gpt/point (:x frame) (:y frame))
(gpt/negate)
(gmt/translate-matrix))
frame-id (:id frame)
modifier-ids (concat [frame-id] (cp/get-children frame-id objects))
update-fn (fn [state shape-id]
(-> state
(assoc-in [shape-id :modifiers :displacement] modifier)))
objects (reduce update-fn objects modifier-ids)
frame (assoc-in frame [:modifiers :displacement] modifier )
width (* (:width frame) zoom)
height (* (:height frame) zoom)
vbox (str "0 0 " (:width frame 0) " " (:height frame 0))
frame-wrapper (mf/use-memo (mf/deps objects)
#(frame-wrapper 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"}
[:& frame-wrapper {:shape frame
:view-box vbox}]]))

View file

@ -14,7 +14,10 @@
[beicon.core :as rx] [beicon.core :as rx]
[uxbox.common.pages :as cp] [uxbox.common.pages :as cp]
[uxbox.main.constants :as c] [uxbox.main.constants :as c]
[uxbox.main.store :as st])) [uxbox.main.store :as st]
[uxbox.common.uuid :as uuid]))
;; ---- Global refs
(def route (def route
(l/derived :route st/state)) (l/derived :route st/state))
@ -28,6 +31,8 @@
(def profile (def profile
(l/derived :profile st/state)) (l/derived :profile st/state))
;; ---- Workspace refs
(def workspace-local (def workspace-local
(l/derived :workspace-local st/state)) (l/derived :workspace-local st/state))
@ -79,6 +84,15 @@
(vec))))] (vec))))]
(l/derived selector st/state =))) (l/derived selector st/state =)))
(def frames
(letfn [(selector [data]
(->> (get-in data [:objects uuid/zero])
:shapes
(map #(get-in data [:objects %]))
(filter #(= (:type %) :frame))
(sort-by :name)))]
(l/derived selector workspace-data)))
(defn is-child-selected? (defn is-child-selected?
[id] [id]
(letfn [(selector [state] (letfn [(selector [state]
@ -120,3 +134,21 @@
(def current-transform (def current-transform
(l/derived :transform workspace-local)) (l/derived :transform workspace-local))
(def options-mode
(l/derived :options-mode workspace-local))
;; ---- Viewer refs
(def viewer-data-ref
(l/derived :viewer-data st/state))
(def viewer-local-ref
(l/derived :viewer-local st/state))
(def interactions-mode
(l/derived :interactions-mode viewer-local-ref))
(def show-interactions?
(l/derived :show-interactions? viewer-local-ref))

View file

@ -17,13 +17,15 @@
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]] [uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]])) [uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
;; --- Circle Wrapper ;; --- Circle Wrapper for workspace
(declare circle-shape) (declare circle-shape)
(mf/defc circle-wrapper (mf/defc circle-wrapper
[{:keys [shape] :as props}] {::mf/wrap-props false}
(let [selected (mf/deref refs/selected-shapes) [props]
(let [shape (unchecked-get props "shape")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape)) selected? (contains? selected (:id shape))
on-mouse-down #(common/on-mouse-down % shape) on-mouse-down #(common/on-mouse-down % shape)
on-context-menu #(common/on-context-menu % shape)] on-context-menu #(common/on-context-menu % shape)]
@ -32,6 +34,36 @@
:on-context-menu on-context-menu} :on-context-menu on-context-menu}
[:& circle-shape {:shape shape}]])) [:& circle-shape {:shape shape}]]))
;; --- Circle Wrapper for viewer
(mf/defc circle-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height]} shape
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& circle-shape {:shape shape}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Circle Shape ;; --- Circle Shape
(mf/defc circle-shape (mf/defc circle-shape

View file

@ -16,6 +16,7 @@
[uxbox.common.data :as d] [uxbox.common.data :as d]
[uxbox.common.spec :as us] [uxbox.common.spec :as us]
[uxbox.main.data.workspace :as dw] [uxbox.main.data.workspace :as dw]
[uxbox.main.data.viewer :as dv]
[uxbox.main.refs :as refs] [uxbox.main.refs :as refs]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.keyboard :as kbd]
@ -66,10 +67,24 @@
(st/emit! (dw/start-move-selected))))))) (st/emit! (dw/start-move-selected)))))))
;; --- Workspace context menu
(defn on-context-menu (defn on-context-menu
[event shape] [event shape]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(let [position (dom/get-client-position event)] (let [position (dom/get-client-position event)]
(st/emit!(dw/show-shape-context-menu {:position position (st/emit! (dw/show-shape-context-menu {:position position
:shape shape})))) :shape shape}))))
;; --- Interaction actions (in viewer mode)
(defn on-mouse-down-viewer
[event {:keys [interactions] :as shape}]
(let [interaction (first (filter #(= (:action-type % :click)) interactions))]
(case (:action-type interaction)
:navigate
(let [frame-id (:destination interaction)]
(st/emit! (dv/go-to-frame frame-id)))
nil)))

View file

@ -32,6 +32,8 @@
(declare frame-shape) (declare frame-shape)
(declare translate-to-frame) (declare translate-to-frame)
;; ---- Frame Wrapper for workspace
(defn frame-wrapper-memo-equals? (defn frame-wrapper-memo-equals?
[np op] [np op]
(let [n-shape (aget np "shape") (let [n-shape (aget np "shape")
@ -114,6 +116,20 @@
{:shape (geom/transform-shape shape) {:shape (geom/transform-shape shape)
:childs childs}]]))))) :childs childs}]])))))
;; ;; --- Frame Wrapper for viewer
;;
;; (mf/defc frame-viewer-wrapper
;; {::mf/wrap-props false}
;; [props]
;; (let [shape (unchecked-get props "shape")
;; on-mouse-down (mf/use-callback
;; (mf/deps shape)
;; #(common/on-mouse-down-viewer % shape))]
;; [:g.shape {:on-mouse-down on-mouse-down}
;; [:& rect-shape {:shape shape}]]))
;; ---- Frame shape
(defn frame-shape (defn frame-shape
[shape-wrapper] [shape-wrapper]
(mf/fnc frame-shape (mf/fnc frame-shape

View file

@ -14,25 +14,60 @@
[uxbox.main.ui.shapes.common :as common] [uxbox.main.ui.shapes.common :as common]
[uxbox.util.interop :as itr])) [uxbox.util.interop :as itr]))
;; --- Icon Wrapper for workspace
;; --- Icon Wrapper
(declare icon-shape) (declare icon-shape)
(mf/defc icon-wrapper (mf/defc icon-wrapper
[{:keys [shape frame] :as props}] {::mf/wrap-props false}
(let [selected (mf/deref refs/selected-shapes) [props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape)) selected? (contains? selected (:id shape))
on-mouse-down #(common/on-mouse-down % shape)] on-mouse-down #(common/on-mouse-down % shape)]
[:g.shape {:class (when selected? "selected") [:g.shape {:class (when selected? "selected")
:on-mouse-down on-mouse-down} :on-mouse-down on-mouse-down}
[:& icon-shape {:shape (geom/transform-shape frame shape)}]])) [:& icon-shape {:shape (geom/transform-shape frame shape)}]]))
;; --- Icon Wrapper for viewer
(mf/defc icon-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
{:keys [x y width height]} shape
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& icon-shape {:shape (geom/transform-shape frame shape)}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Icon Shape ;; --- Icon Shape
(mf/defc icon-shape (mf/defc icon-shape
[{:keys [shape] :as props}] {::mf/wrap-props false}
(let [{:keys [id x y width height metadata rotation content] :as shape} shape [props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height metadata rotation content]} shape
transform (when (and rotation (pos? rotation)) transform (when (and rotation (pos? rotation))
(str/format "rotate(%s %s %s)" (str/format "rotate(%s %s %s)"
rotation rotation

View file

@ -17,13 +17,15 @@
[uxbox.util.interop :as itr] [uxbox.util.interop :as itr]
[uxbox.util.geom.matrix :as gmt])) [uxbox.util.geom.matrix :as gmt]))
;; --- Image Wrapper ;; --- Image Wrapper for workspace
(declare image-shape) (declare image-shape)
(mf/defc image-wrapper (mf/defc image-wrapper
[{:keys [shape] :as props}] {::mf/wrap-props false}
(let [selected (mf/deref refs/selected-shapes) [props]
(let [shape (unchecked-get props "shape")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape)) selected? (contains? selected (:id shape))
on-mouse-down (mf/use-callback on-mouse-down (mf/use-callback
(mf/deps shape) (mf/deps shape)
@ -38,18 +40,48 @@
:on-context-menu on-context-menu} :on-context-menu on-context-menu}
[:& image-shape {:shape shape}]])) [:& image-shape {:shape shape}]]))
;; --- Image Wrapper for viewer
(mf/defc image-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [x y width height]} shape
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& image-shape {:shape shape}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Image Shape ;; --- Image Shape
(mf/defc image-shape (mf/defc image-shape
[{:keys [shape] :as props}] {::mf/wrap-props false}
(let [{:keys [id x y width height rotation metadata]} shape [props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height rotation metadata]} shape
transform (geom/transform-matrix shape) transform (geom/transform-matrix shape)
uri (if (or (> (:thumb-width metadata) width) uri (if (or (> (:thumb-width metadata) width)
(> (:thumb-height metadata) height)) (> (:thumb-height metadata) height))
(:thumb-uri metadata) (:thumb-uri metadata)
(:uri metadata)) (:uri metadata))
props (-> (attrs/extract-style-attrs shape) props (-> (attrs/extract-style-attrs shape)
(itr/obj-assign! (itr/obj-assign!
#js {:x x #js {:x x

View file

@ -19,13 +19,15 @@
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]] [uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]])) [uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
;; --- Path Wrapper ;; --- Path Wrapper for workspace
(declare path-shape) (declare path-shape)
(mf/defc path-wrapper (mf/defc path-wrapper
[{:keys [shape] :as props}] {::mf/wrap-props false}
(let [selected (mf/deref refs/selected-shapes) [props]
(let [shape (unchecked-get props "shape")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape)) selected? (contains? selected (:id shape))
on-mouse-down (mf/use-callback on-mouse-down (mf/use-callback
(mf/deps shape) (mf/deps shape)
@ -44,6 +46,35 @@
:on-context-menu on-context-menu} :on-context-menu on-context-menu}
[:& path-shape {:shape shape :background? true}]])) [:& path-shape {:shape shape :background? true}]]))
;; --- Path Wrapper for viewer
(mf/defc path-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [x y width height]} (geom/shape->rect-shape shape)
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& path-shape {:shape shape}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Path Shape ;; --- Path Shape
(defn- render-path (defn- render-path
@ -68,8 +99,11 @@
(recur buffer (inc index))))))) (recur buffer (inc index)))))))
(mf/defc path-shape (mf/defc path-shape
[{:keys [shape background?] :as props}] {::mf/wrap-props false}
(let [{:keys [id x y width height]} (geom/shape->rect-shape shape) [props]
(let [shape (unchecked-get props "shape")
background? (unchecked-get props "background?")
{:keys [id x y width height]} (geom/shape->rect-shape shape)
transform (geom/transform-matrix shape) transform (geom/transform-matrix shape)
pdata (render-path shape) pdata (render-path shape)
props (-> (attrs/extract-style-attrs shape) props (-> (attrs/extract-style-attrs shape)

View file

@ -17,17 +17,17 @@
[uxbox.util.interop :as itr] [uxbox.util.interop :as itr]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]])) [uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
;; --- Rect Wrapper
(declare rect-shape) (declare rect-shape)
;; --- Rect Wrapper for workspace
(mf/defc rect-wrapper (mf/defc rect-wrapper
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [shape (unchecked-get props "shape") (let [shape (unchecked-get props "shape")
on-mouse-down (mf/use-callback on-mouse-down (mf/use-callback
(mf/deps shape) (mf/deps shape)
#(common/on-mouse-down % shape)) #(common/on-mouse-down % shape))
on-context-menu (mf/use-callback on-context-menu (mf/use-callback
(mf/deps shape) (mf/deps shape)
#(common/on-context-menu % shape))] #(common/on-context-menu % shape))]
@ -35,6 +35,36 @@
:on-context-menu on-context-menu} :on-context-menu on-context-menu}
[:& rect-shape {:shape shape}]])) [:& rect-shape {:shape shape}]]))
;; --- Rect Wrapper for viewer
(mf/defc rect-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [x y width height]} shape
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& rect-shape {:shape shape}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Rect Shape ;; --- Rect Shape
(mf/defc rect-shape (mf/defc rect-shape

View file

@ -39,15 +39,17 @@
(dom/stop-propagation event) (dom/stop-propagation event)
(common/on-mouse-down event shape))) (common/on-mouse-down event shape)))
;; --- Text Wrapper ;; --- Text Wrapper for workspace
(declare text-shape-html) (declare text-shape-html)
(declare text-shape-edit) (declare text-shape-edit)
(declare text-shape) (declare text-shape)
(mf/defc text-wrapper (mf/defc text-wrapper
[{:keys [shape] :as props}] {::mf/wrap-props false}
(let [{:keys [id x1 y1 content group]} shape [props]
(let [shape (unchecked-get props "shape")
{:keys [id x1 y1 content group]} shape
selected (mf/deref refs/selected-shapes) selected (mf/deref refs/selected-shapes)
edition (mf/deref refs/selected-edition) edition (mf/deref refs/selected-edition)
edition? (= edition id) edition? (= edition id)
@ -72,6 +74,35 @@
[:& text-shape {:shape shape [:& text-shape {:shape shape
:selected? selected?}])])) :selected? selected?}])]))
;; --- Text Wrapper for viewer
(mf/defc text-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [x y width height]} shape
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& text-shape {:shape shape}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Text Editor Rendering ;; --- Text Editor Rendering
(defn- generate-root-styles (defn- generate-root-styles
@ -342,8 +373,11 @@
(render-text-node root))) (render-text-node root)))
(mf/defc text-shape (mf/defc text-shape
[{:keys [shape selected?] :as props}] {::mf/wrap-props false}
(let [{:keys [id x y width height rotation content]} shape] [props]
(let [shape (unchecked-get props "shape")
shape (unchecked-get props "selected?")
{:keys [id x y width height rotation content]} shape]
[:foreignObject {:x x [:foreignObject {:x x
:y y :y y
:transform (geom/transform-matrix shape) :transform (geom/transform-matrix shape)

View file

@ -17,13 +17,15 @@
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.common.exceptions :as ex] [uxbox.common.exceptions :as ex]
[uxbox.main.data.viewer :as dv] [uxbox.main.data.viewer :as dv]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.ui.components.dropdown :refer [dropdown]] [uxbox.main.ui.components.dropdown :refer [dropdown]]
[uxbox.main.ui.hooks :as hooks] [uxbox.main.ui.hooks :as hooks]
[uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.messages :refer [messages]] [uxbox.main.ui.messages :refer [messages]]
[uxbox.main.ui.viewer.header :refer [header]] [uxbox.main.ui.viewer.header :refer [header]]
[uxbox.main.ui.viewer.thumbnails :refer [thumbnails-panel frame-svg]] [uxbox.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
[uxbox.main.ui.viewer.frame-viewer :refer [frame-viewer-svg]]
[uxbox.util.data :refer [classnames]] [uxbox.util.data :refer [classnames]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.i18n :as i18n :refer [t tr]]) [uxbox.util.i18n :as i18n :refer [t tr]])
@ -46,9 +48,9 @@
[:span (t locale "viewer.frame-not-found")]] [:span (t locale "viewer.frame-not-found")]]
:else :else
[:& frame-svg {:frame frame [:& frame-viewer-svg {:frame frame
:zoom zoom :zoom zoom
:objects objects}])])) :objects objects}])]))
(mf/defc viewer-content (mf/defc viewer-content
[{:keys [data local index] :as props}] [{:keys [data local index] :as props}]
@ -56,6 +58,13 @@
[toggle-fullscreen fullscreen?] (hooks/use-fullscreen container) [toggle-fullscreen fullscreen?] (hooks/use-fullscreen container)
on-click
(fn [event]
(dom/stop-propagation event)
(let [mode (get local :interactions-mode)]
(when (= mode :show-on-click)
(st/emit! dv/flash-interactions))))
on-mouse-wheel on-mouse-wheel
(fn [event] (fn [event]
(when (kbd/ctrl? event) (when (kbd/ctrl? event)
@ -65,7 +74,6 @@
(st/emit! dv/decrease-zoom) (st/emit! dv/decrease-zoom)
(st/emit! dv/increase-zoom))))) (st/emit! dv/increase-zoom)))))
on-mount on-mount
(fn [] (fn []
;; bind with passive=false to allow the event to be cancelled ;; bind with passive=false to allow the event to be cancelled
@ -88,7 +96,7 @@
:fullscreen? fullscreen? :fullscreen? fullscreen?
:local local :local local
:index index}] :index index}]
[:div.viewer-content [:div.viewer-content {:on-click on-click}
(when (:show-thumbnails local) (when (:show-thumbnails local)
[:& thumbnails-panel {:index index [:& thumbnails-panel {:index index
:data data}]) :data data}])
@ -99,20 +107,14 @@
;; --- Component: Viewer Page ;; --- Component: Viewer Page
(def viewer-data-ref
(l/derived :viewer-data st/state))
(def viewer-local-ref
(l/derived :viewer-local st/state))
(mf/defc viewer-page (mf/defc viewer-page
[{:keys [page-id index token] :as props}] [{:keys [page-id index token] :as props}]
(mf/use-effect (mf/use-effect
(mf/deps page-id token) (mf/deps page-id token)
#(st/emit! (dv/initialize page-id token))) #(st/emit! (dv/initialize page-id token)))
(let [data (mf/deref viewer-data-ref) (let [data (mf/deref refs/viewer-data-ref)
local (mf/deref viewer-local-ref)] local (mf/deref refs/viewer-local-ref)]
(when data (when data
[:& viewer-content {:index index [:& viewer-content {:index index
:local local :local local

View file

@ -0,0 +1,104 @@
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.viewer.frame-viewer
"The main container for a frame in viewer mode"
(:require
[rumext.alpha :as mf]
[uxbox.common.uuid :as uuid]
[uxbox.util.math :as mth]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.geom.matrix :as gmt]
[uxbox.common.pages :as cp]
[uxbox.main.ui.shapes.frame :as frame]
[uxbox.main.ui.shapes.circle :as circle]
[uxbox.main.ui.shapes.icon :as icon]
[uxbox.main.ui.shapes.image :as image]
[uxbox.main.ui.shapes.path :as path]
[uxbox.main.ui.shapes.rect :as rect]
[uxbox.main.ui.shapes.text :as text]
[uxbox.main.ui.shapes.group :as group]))
(declare shape-wrapper)
(defn frame-wrapper
[objects]
(mf/fnc frame-wrapper
[{:keys [shape] :as props}]
(let [childs (mapv #(get objects %)
(:shapes shape))
shape-wrapper (mf/use-memo (mf/deps objects)
#(shape-wrapper objects))
frame-shape (mf/use-memo (mf/deps objects)
#(frame/frame-shape shape-wrapper))
shape (geom/transform-shape shape)]
[:& frame-shape {:shape shape :childs childs}])))
(defn group-wrapper
[objects]
(mf/fnc group-wrapper
[{:keys [shape frame] :as props}]
(let [children (mapv #(get objects %)
(:shapes shape))
shape-wrapper (mf/use-memo (mf/deps objects)
#(shape-wrapper objects))
group-shape (mf/use-memo (mf/deps objects)
#(group/group-shape shape-wrapper))]
[:& group-shape {:frame frame
:shape shape
:children children}])))
(defn shape-wrapper
[objects]
(mf/fnc shape-wrapper
[{:keys [frame shape] :as props}]
(let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper objects))]
(when (and shape (not (:hidden shape)))
(let [shape (geom/transform-shape frame shape)
opts #js {:shape shape}]
(case (:type shape)
:curve [:> path/path-viewer-wrapper opts]
:text [:> text/text-viewer-wrapper opts]
:icon [:> icon/icon-viewer-wrapper opts]
:rect [:> rect/rect-viewer-wrapper opts]
:path [:> path/path-viewer-wrapper opts]
:image [:> image/image-viewer-wrapper opts]
:circle [:> circle/circle-viewer-wrapper opts]
:group [:> group-wrapper {:shape shape :frame frame}]
nil))))))
(mf/defc frame-viewer-svg
{::mf/wrap [mf/memo]}
[{:keys [objects frame zoom] :or {zoom 1} :as props}]
(let [modifier (-> (gpt/point (:x frame) (:y frame))
(gpt/negate)
(gmt/translate-matrix))
frame-id (:id frame)
modifier-ids (concat [frame-id] (cp/get-children frame-id objects))
update-fn (fn [state shape-id]
(-> state
(assoc-in [shape-id :modifiers :displacement] modifier)))
objects (reduce update-fn objects modifier-ids)
frame (assoc-in frame [:modifiers :displacement] modifier )
width (* (:width frame) zoom)
height (* (:height frame) zoom)
vbox (str "0 0 " (:width frame 0) " " (:height frame 0))
frame-wrapper (mf/use-memo (mf/deps objects)
#(frame-wrapper 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"}
[:& frame-wrapper {:shape frame
:view-box vbox}]]))

View file

@ -24,6 +24,28 @@
[uxbox.common.uuid :as uuid] [uxbox.common.uuid :as uuid]
[uxbox.util.webapi :as wapi])) [uxbox.util.webapi :as wapi]))
(mf/defc interactions-menu
[{:keys [interactions-mode] :as props}]
(let [show-dropdown? (mf/use-state false)
locale (i18n/use-locale)
on-select-mode #(st/emit! (dv/set-interactions-mode %))]
[:div.header-icon
[:a {:on-click #(swap! show-dropdown? not)} i/eye
[:& dropdown {:show @show-dropdown?
:on-close #(swap! show-dropdown? not)}
[:ul.custom-select-dropdown
[:li {:key :hide
:class (classnames :selected (= interactions-mode :hide))
:on-click #(on-select-mode :hide)}
(t locale "viewer.header.dont-show-interactions")]
[:li {:key :show
:class (classnames :selected (= interactions-mode :show))
:on-click #(on-select-mode :show)}
(t locale "viewer.header.show-interactions")]
[:li {:key :show-on-click
:class (classnames :selected (= interactions-mode :show-on-click))
:on-click #(on-select-mode :show-on-click)}
(t locale "viewer.header.show-interactions-on-click")]]]]]))
(mf/defc share-link (mf/defc share-link
[{:keys [page] :as props}] [{:keys [page] :as props}]
@ -77,6 +99,8 @@
total (count frames) total (count frames)
on-click #(st/emit! dv/toggle-thumbnails-panel) on-click #(st/emit! dv/toggle-thumbnails-panel)
interactions-mode (:interactions-mode local)
locale (i18n/use-locale) locale (i18n/use-locale)
profile (mf/deref refs/profile) profile (mf/deref refs/profile)
@ -105,6 +129,7 @@
[:span.counters (str (inc index) " / " total)]] [:span.counters (str (inc index) " / " total)]]
[:div.options-zone [:div.options-zone
[:& interactions-menu {:interactions-mode interactions-mode}]
(when-not anonymous? (when-not anonymous?
[:& share-link {:page (:page data)}]) [:& share-link {:page (:page data)}])
(when-not anonymous? (when-not anonymous?
@ -127,4 +152,3 @@
i/full-screen)] i/full-screen)]
]])) ]]))

View file

@ -15,7 +15,6 @@
[rumext.alpha :as mf] [rumext.alpha :as mf]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.common.data :as d] [uxbox.common.data :as d]
[uxbox.common.pages :as cp]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.data.viewer :as dv] [uxbox.main.data.viewer :as dv]
[uxbox.main.ui.components.dropdown :refer [dropdown']] [uxbox.main.ui.components.dropdown :refer [dropdown']]
@ -78,37 +77,6 @@
[:div.thumbnails-list-inside {:style {:right (str (* @offset 152) "px")}} [:div.thumbnails-list-inside {:style {:right (str (* @offset 152) "px")}}
children]]]))) children]]])))
(mf/defc frame-svg
{::mf/wrap [mf/memo]}
[{:keys [objects frame zoom] :or {zoom 1} :as props}]
(let [modifier (-> (gpt/point (:x frame) (:y frame))
(gpt/negate)
(gmt/translate-matrix))
frame-id (:id frame)
modifier-ids (concat [frame-id] (cp/get-children frame-id objects))
update-fn (fn [state shape-id]
(-> state
(assoc-in [shape-id :modifiers :displacement] modifier)))
objects (reduce update-fn objects modifier-ids)
frame (assoc-in frame [:modifiers :displacement] modifier )
width (* (:width frame) zoom)
height (* (:height frame) zoom)
vbox (str "0 0 " (:width frame 0) " " (:height frame 0))
frame-wrapper (mf/use-memo (mf/deps objects)
#(exports/frame-wrapper 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"}
[:& frame-wrapper {:shape frame
:view-box vbox}]]))
(mf/defc thumbnails-summary (mf/defc thumbnails-summary
[{:keys [on-toggle-expand on-close total] :as props}] [{:keys [on-toggle-expand on-close total] :as props}]
[:div.thumbnails-summary [:div.thumbnails-summary
@ -122,7 +90,7 @@
[:div.thumbnail-item {:on-click #(on-click % index)} [:div.thumbnail-item {:on-click #(on-click % index)}
[:div.thumbnail-preview [:div.thumbnail-preview
{:class (classnames :selected selected?)} {:class (classnames :selected selected?)}
[:& frame-svg {:frame frame :objects objects}]] [:& exports/frame-svg {:frame frame :objects objects}]]
[:div.thumbnail-info [:div.thumbnail-info
[:span.name (:name frame)]]]) [:span.name (:name frame)]]])

View file

@ -25,6 +25,8 @@
[uxbox.main.ui.workspace.sidebar.options.image :as image] [uxbox.main.ui.workspace.sidebar.options.image :as image]
[uxbox.main.ui.workspace.sidebar.options.text :as text] [uxbox.main.ui.workspace.sidebar.options.text :as text]
[uxbox.main.ui.workspace.sidebar.options.page :as page] [uxbox.main.ui.workspace.sidebar.options.page :as page]
[uxbox.main.ui.workspace.sidebar.options.interactions :refer [interactions-menu]]
[uxbox.main.ui.components.tab-container :refer [tab-container tab-element]]
[uxbox.util.i18n :refer [tr]])) [uxbox.util.i18n :refer [tr]]))
;; --- Options ;; --- Options
@ -45,29 +47,43 @@
:image [:& image/options {:shape shape}] :image [:& image/options {:shape shape}]
nil)]) nil)])
(mf/defc shape-options-wrapper
[{:keys [shape-id page-id] :as props}]
(let [shape-iref (-> (mf/deps shape-id page-id)
(mf/use-memo
#(-> (l/in [:objects shape-id])
(l/derived refs/workspace-data))))
shape (mf/deref shape-iref)]
[:& shape-options {:shape shape}]))
(mf/defc options-toolbox (mf/defc options-toolbox
{:wrap [mf/memo]} {:wrap [mf/memo]}
[{:keys [page selected] :as props}] [{:keys [page selected] :as props}]
(let [close #(st/emit! (udw/toggle-layout-flag :element-options)) (let [close #(st/emit! (udw/toggle-layout-flag :element-options))
selected (mf/deref refs/selected-shapes)] on-change-tab #(st/emit! (udw/set-options-mode %))
[:div.element-options.tool-window
;; [:div.tool-window-bar options-mode (mf/deref refs/options-mode)
;; [:div.tool-window-icon i/options]
;; [:span (tr "ds.settings.element-options")] selected (mf/deref refs/selected-shapes)
;; [:div.tool-window-close {:on-click close} i/close]] shape-id (first selected)
[:& align-options] page-id (:id page)
[:div.tool-window-content shape-iref (-> (mf/deps shape-id page-id)
[:div.element-options (mf/use-memo
(if (= (count selected) 1) #(-> (l/in [:objects shape-id])
[:& shape-options-wrapper {:shape-id (first selected) (l/derived refs/workspace-data))))
:page-id (:id page)}] shape (mf/deref shape-iref)]
[:& page/options {:page page}])]]]))
[:div.tool-window
;; [:div.tool-window-bar
;; [:div.tool-window-icon i/options]
;; [:span (tr "ds.settings.element-options")]
;; [:div.tool-window-close {:on-click close} i/close]]
[:div.tool-window-content
[:& tab-container {:on-change-tab on-change-tab :selected options-mode}
[:& tab-element
{:id :design :title (tr "workspace.options.design")}
[:div.element-options
[:& align-options]
[:div
(if (= (count selected) 1)
[:& shape-options {:shape shape}]
[:& page/options {:page page}])]]]
[:& tab-element
{:id :prototype :title (tr "workspace.options.prototype")}
[:div.element-options
[:& interactions-menu {:shape shape}]]]]
]]))

View file

@ -64,7 +64,7 @@
delta (if (= attr :x) delta (if (= attr :x)
(gpt/point (math/neg (- pval cval)) 0) (gpt/point (math/neg (- pval cval)) 0)
(gpt/point 0 (math/neg (- pval cval))))] (gpt/point 0 (math/neg (- pval cval))))]
;; TODO: Change so not apply the modifiers until blur ;; TODO: Change so not apply the modifiers until blur
(st/emit! (udw/set-modifiers #{(:id shape)} {:displacement delta}) (st/emit! (udw/set-modifiers #{(:id shape)} {:displacement delta})
(udw/apply-modifiers #{(:id shape)})))) (udw/apply-modifiers #{(:id shape)}))))
@ -94,9 +94,7 @@
(:name size-preset) (:name size-preset)
[:span (:width size-preset) " x " (:height size-preset)]]))]]] [:span (:width size-preset) " x " (:height size-preset)]]))]]]
[:span.orientation-icon {on-click #(on-orientation-clicked :vert)} i/size-vert] [:span.orientation-icon {on-click #(on-orientation-clicked :vert)} i/size-vert]
[:span.orientation-icon {on-click #(on-orientation-clicked :horiz)} i/size-horiz] [:span.orientation-icon {on-click #(on-orientation-clicked :horiz)} i/size-horiz]]
]
;; WIDTH & HEIGHT ;; WIDTH & HEIGHT
[:div.row-flex [:div.row-flex

View file

@ -14,10 +14,61 @@
[uxbox.main.refs :as refs] [uxbox.main.refs :as refs]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.ui.colorpicker :as cp] [uxbox.main.ui.colorpicker :as cp]
[uxbox.main.ui.components.dropdown :refer [dropdown]]
[uxbox.util.data :refer [read-string]] [uxbox.util.data :refer [read-string]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.i18n :refer [tr]])) [uxbox.util.i18n :refer [tr]]))
(mf/defc interactions-menu
[{:keys [shape] :as props}]
(let [interaction (first (:interactions shape)) ;; TODO: in the future we may have several interactions in one shape
destination (first (deref (refs/objects-by-id [(:destination interaction)])))
frames (mf/deref refs/frames)
show-frames-dropdown? (mf/use-state false)
on-set-blur
#(reset! show-frames-dropdown? false)
on-select-destination
#(if (nil? %)
(st/emit! (dw/update-shape (:id shape) {:interactions []}))
(st/emit! (dw/update-shape (:id shape) {:interactions [{:event-type :click
:action-type :navigate
:destination %}]})))
on-navigate
#(st/emit! (dw/select-shapes #{(:id destination)}))]
(if (not shape)
[:*
[: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")]]
[:div.element-set {:on-blur on-set-blur}
[:div.element-set-title
[:span (tr "workspace.options.navigate-to")]]
[:div.element-set-content
[:div.row-flex
[:div.custom-select.flex-grow {:on-click #(reset! show-frames-dropdown? true)}
(if destination
[:span (:name destination)]
[:span (tr "workspace.options.select-artboard")])
[:span.dropdown-button i/arrow-down]
[:& dropdown {:show @show-frames-dropdown?
:on-close #(reset! show-frames-dropdown? false)}
[:ul.custom-select-dropdown
[:li.dropdown-separator {:key nil
:on-click #(on-select-destination nil)}
(tr "workspace.options.none")]
(for [frame frames]
(when (not= (:id frame) (:id shape)) ; A frame cannot navigate to itself
[:li {:key (:id frame)
:on-click #(on-select-destination (:id frame))}
(:name frame)]))]]]
[:span.navigate-icon {on-click on-navigate} i/navigate]]]])))
;; --- Helpers ;; --- Helpers
;; (defn- on-change ;; (defn- on-change
@ -469,25 +520,25 @@
;; --- Interactions Menu ;; --- Interactions Menu
(def +initial-form+ ;; (def +initial-form+
{:trigger :click ;; {:trigger :click
:action :show}) ;; :action :show})
;;
(mf/defc interactions-menu ;; (mf/defc interactions-menu
[{:keys [menu shape] :as props}] ;; [{:keys [menu shape] :as props}]
#_(let [form (mf/use-state nil) ;; #_(let [form (mf/use-state nil)
interactions (:interactions shape)] ;; interactions (:interactions shape)]
[:div.element-set {:key (str (:id menu))} ;; [:div.element-set {:key (str (:id menu))}
[:div.element-set-title (:name menu)] ;; [:div.element-set-title (:name menu)]
[:div.element-set-content ;; [:div.element-set-content
(if form ;; (if form
[:& interactions-form {:form form :shape shape}] ;; [:& interactions-form {:form form :shape shape}]
[:div ;; [:div
[:& interactions-list {:form form :shape shape}] ;; [:& interactions-list {:form form :shape shape}]
[:input.btn-primary.btn-small ;; [:input.btn-primary.btn-small
{:value "New interaction" ;; {:value "New interaction"
:on-click #(reset! form +initial-form+) ;; :on-click #(reset! form +initial-form+)
:type "button"}]])]])) ;; :type "button"}]])]]))
;; --- Not implemented stuff ;; --- Not implemented stuff

View file

@ -9,8 +9,8 @@
(defn debug-none! [] (reset! *debug* #{})) (defn debug-none! [] (reset! *debug* #{}))
(defn debug! [option] (swap! *debug* conj option)) (defn debug! [option] (swap! *debug* conj option))
(defn -debug! [option] (swap! *debug* disj option)) (defn -debug! [option] (swap! *debug* disj option))
(defn debug? [option] (@*debug* option))
(defn ^:export debug? [option] (@*debug* option))
(defn ^:export toggle-debug [name] (let [option (keyword name)] (defn ^:export toggle-debug [name] (let [option (keyword name)]
(if (debug? option) (if (debug? option)
@ -18,7 +18,7 @@
(debug! option)))) (debug! option))))
(defn ^:export debug-all [name] (debug-all!)) (defn ^:export debug-all [name] (debug-all!))
(defn tap (defn ^:export tap
"Transducer function that can execute a side-effect `effect-fn` per input" "Transducer function that can execute a side-effect `effect-fn` per input"
[effect-fn] [effect-fn]
@ -30,7 +30,7 @@
(effect-fn input) (effect-fn input)
(rf result input))))) (rf result input)))))
(defn logjs (defn ^:export logjs
([str] (tap (partial logjs str))) ([str] (tap (partial logjs str)))
([str val] ([str val]
(js/console.log str (clj->js val)) (js/console.log str (clj->js val))