0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-13 07:21:40 -05:00

Merge pull request #195 from uxbox/231/interactions

231/interactions
This commit is contained in:
Andrey Antukh 2020-05-05 15:53:52 +02:00 committed by GitHub
commit 0aeb966b11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1024 additions and 226 deletions

View file

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

View file

@ -135,6 +135,19 @@
::grid-x
::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
(s/def ::blocked boolean?)
(s/def ::collapsed boolean?)
@ -194,7 +207,8 @@
::stroke-width
::stroke-alignment
::text-align
::width ::height]))
::width ::height
::interactions]))
(s/def ::minimal-shape
(s/keys :req-un [::type ::name]

View file

@ -26,12 +26,19 @@ You can deactivate debug mode with
(-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
(dump-state) ; to print in console all the global state
(dump-objects) ; to print in console all objects in workspace
(ns uxbox.util.debug)
(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.
(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" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:61", "src/uxbox/main/ui/dashboard/grid.cljs:92" ],
"translations" : {
@ -259,7 +258,7 @@
}
},
"ds.search.placeholder" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:174" ],
"used-in" : [ "src/uxbox/main/ui/dashboard/sidebar.cljs:168" ],
"translations" : {
"en" : "Search...",
"fr" : "Rechercher..."
@ -329,28 +328,28 @@
}
},
"errors.auth.unauthorized" : {
"used-in" : [ "src/uxbox/main/data/auth.cljs:56" ],
"used-in" : [ "src/uxbox/main/data/auth.cljs:57" ],
"translations" : {
"en" : "Username or password seems to be wrong.",
"fr" : "Le nom d'utilisateur ou le mot de passe semble être faux."
}
},
"errors.generic" : {
"used-in" : [ "src/uxbox/main/ui.cljs:175" ],
"used-in" : [ "src/uxbox/main/ui.cljs:179" ],
"translations" : {
"en" : "Something wrong has happened.",
"fr" : "Quelque chose c'est mal passé."
}
},
"errors.network" : {
"used-in" : [ "src/uxbox/main/ui.cljs:169" ],
"used-in" : [ "src/uxbox/main/ui.cljs:173" ],
"translations" : {
"en" : "Unable to connect to backend server.",
"fr" : "Impossible de se connecter au serveur principal."
}
},
"header.sitemap" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:99" ],
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:74" ],
"translations" : {
"en" : null,
"fr" : null
@ -580,7 +579,7 @@
}
},
"settings.password" : {
"used-in" : [ "src/uxbox/main/ui/settings/header.cljs:34" ],
"used-in" : [ "src/uxbox/main/ui/settings/header.cljs:36" ],
"translations" : {
"en" : "PASSWORD",
"fr" : "MOT DE PASSE"
@ -622,14 +621,14 @@
}
},
"settings.profile" : {
"used-in" : [ "src/uxbox/main/ui/settings/header.cljs:30" ],
"used-in" : [ "src/uxbox/main/ui/settings/header.cljs:32" ],
"translations" : {
"en" : "PROFILE",
"fr" : "PROFIL"
}
},
"settings.profile.lang" : {
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:89" ],
"used-in" : [ "src/uxbox/main/ui/settings/profile.cljs:88" ],
"translations" : {
"en" : "Default language",
"fr" : "Langue par défaut"
@ -643,109 +642,127 @@
}
},
"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" : {
"en" : "Name, username and email",
"fr" : "Nom, nom d'utilisateur et adresse email"
}
},
"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" : {
"en" : "Default theme",
"fr" : "Thème par défaut"
}
},
"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" : {
"en" : "Your avatar",
"fr" : "Votre avatar"
}
},
"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" : {
"en" : null,
"fr" : null
}
},
"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" : {
"en" : "Your name",
"fr" : "Votre nom complet"
}
},
"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" : {
"en" : "Update settings",
"fr" : "Mettre à jour les paramètres"
}
},
"viewer.empty-state" : {
"used-in" : [ "src/uxbox/main/ui/viewer.cljs:43" ],
"used-in" : [ "src/uxbox/main/ui/viewer.cljs:44" ],
"translations" : {
"en" : "No frames found on the page."
}
},
"viewer.frame-not-found" : {
"used-in" : [ "src/uxbox/main/ui/viewer.cljs:47" ],
"used-in" : [ "src/uxbox/main/ui/viewer.cljs:48" ],
"translations" : {
"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" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:112" ],
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:137" ],
"translations" : {
"en" : "Edit page"
}
},
"viewer.header.fullscreen" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:123" ],
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:148" ],
"translations" : {
"en" : "Full Screen"
}
},
"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" : {
"en" : "Copy 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" : {
"en" : "Create link"
}
},
"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" : {
"en" : "Share link will apear here"
}
},
"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" : {
"en" : "Remove link"
}
},
"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" : {
"en" : "Anyone with the link will have access"
}
},
"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" : {
"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" : {
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:97" ],
"used-in" : [ "src/uxbox/main/ui/viewer/header.cljs:121" ],
"translations" : {
"en" : "Sitemap"
}
@ -799,117 +816,123 @@
}
},
"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" : {
"en" : "Hide grid"
}
},
"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" : {
"en" : "Hide layers"
}
},
"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" : {
"en" : "Hide libraries"
}
},
"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" : {
"en" : "Hide color palette"
}
},
"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" : {
"en" : "Hide rules"
}
},
"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" : {
"en" : "Show grid"
}
},
"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" : {
"en" : "Show layers"
}
},
"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" : {
"en" : "Show libraries"
}
},
"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" : {
"en" : "Show color palette"
}
},
"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" : {
"en" : "Show rules"
}
},
"workspace.header.viewer" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:176" ],
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:153" ],
"translations" : {
"en" : "View mode (Ctrl + P)",
"fr" : "Mode visualisation (Ctrl + P)"
}
},
"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" : {
"en" : "All libraries"
}
},
"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" : {
"en" : "Icons"
}
},
"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" : {
"en" : "Images"
}
},
"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" : {
"en" : "Libraries"
}
},
"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" : {
"en" : "My libraries"
}
},
"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" : {
"en" : "Store libraries"
}
},
"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" : {
"en" : "Color",
"fr" : "Couleur"
}
},
"workspace.options.design" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:76" ],
"translations" : {
"en" : "Design"
}
},
"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" : {
"en" : "Fill",
"fr" : "Fond"
@ -922,33 +945,121 @@
},
"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" : {
"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" : {
"en" : "Text",
"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" : {
"translations" : {
"en" : "Font Size & Weight",
@ -957,7 +1068,7 @@
"unused" : true
},
"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" : {
"en" : "Grid settings",
"fr" : "Paramètres de la grille"
@ -977,6 +1088,18 @@
},
"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" : {
"translations" : {
"en" : "Opacity",
@ -985,21 +1108,27 @@
"unused" : true
},
"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" : {
"en" : "Position",
"fr" : "Position"
}
},
"workspace.options.prototype" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options.cljs:85" ],
"translations" : {
"en" : "Prototype"
}
},
"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" : {
"en" : "Radius",
"fr" : "TODO"
}
},
"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" : {
"en" : "Rotation",
"fr" : "TODO"
@ -1012,67 +1141,79 @@
},
"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" : {
"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" : {
"en" : "Size",
"fr" : "Taille"
}
},
"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" : {
"en" : "Size presets"
}
},
"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" : {
"en" : "Stroke",
"fr" : null
}
},
"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" : {
"en" : "Center"
}
},
"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" : {
"en" : "Dashed",
"fr" : "Tiré"
}
},
"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" : {
"en" : "Dotted",
"fr" : "Pointillé"
}
},
"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" : {
"en" : "Inside"
}
},
"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" : {
"en" : "Mixed",
"fr" : "Mixte"
}
},
"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" : {
"en" : "Outside"
}
},
"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" : {
"en" : "Solid",
"fr" : "Solide"
@ -1092,6 +1233,12 @@
},
"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" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/icons.cljs:88" ],
"translations" : {
@ -1107,7 +1254,7 @@
"unused" : true
},
"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" : {
"en" : "Pages",
"fr" : "Pages"
@ -1211,7 +1358,7 @@
}
},
"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" : {
"en" : "Click to close the path"
}

View file

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

View file

@ -7,12 +7,9 @@
.element-options {
display: flex;
flex-direction: column;
width: 100%;
> div {
width: 100%;
}
.element-icons {
background-color: $color-gray-60;
border: 1px solid $color-gray-60;
@ -307,6 +304,10 @@
margin-left: auto;
}
&.dropdown-separator:not(:last-child) {
border-bottom: 1px solid $color-gray-10;
}
&.dropdown-label:not(:first-child) {
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 {
align-items: center;
display: flex;

View file

@ -4,3 +4,21 @@
//
// Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
// 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 {
display: flex;
flex-wrap: wrap;
flex-direction: column;
overflow-y: auto;
height: 100%;
width: 100%;
}
.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 {
align-items: center;
cursor: pointer;
@ -88,7 +112,7 @@
.options-zone {
align-items: center;
display: flex;
width: 300px;
width: 350px;
justify-content: flex-end;
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 {
left : 150px;
top: 45px;

View file

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

View file

@ -31,6 +31,8 @@
(s/def ::file (s/keys :req-un [::id ::name]))
(s/def ::page (s/keys :req-un [::id ::name ::cp/data]))
(s/def ::interactions-mode #{:hide :show :show-on-click})
(s/def ::bundle
(s/keys :req-un [::project ::file ::page]))
@ -45,7 +47,10 @@
(ptk/reify ::initialize
ptk/UpdateEvent
(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
(watch [_ state stream]
@ -178,6 +183,52 @@
(when (< index (dec total))
(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

View file

@ -68,13 +68,16 @@
:element-options
:rules})
(s/def ::options-mode #{:design :prototype})
(def workspace-default
{:zoom 1
:flags #{}
:selected #{}
:drawing nil
:drawing-tool nil
:tooltip nil})
:tooltip nil
:options-mode :design})
(def initialize-layout
(ptk/reify ::initialize-layout
@ -243,6 +246,16 @@
(conj flags flag)))))]
(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
(defn assign-cursor-tooltip

View file

@ -9,6 +9,7 @@
(:require
[rumext.alpha :as mf]
[uxbox.common.uuid :as uuid]
[uxbox.common.pages :as cp]
[uxbox.util.math :as mth]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.geom.point :as gpt]
@ -110,3 +111,34 @@
[:& shape-wrapper {:shape 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]
[uxbox.common.pages :as cp]
[uxbox.main.constants :as c]
[uxbox.main.store :as st]))
[uxbox.main.store :as st]
[uxbox.common.uuid :as uuid]))
;; ---- Global refs
(def route
(l/derived :route st/state))
@ -28,6 +31,8 @@
(def profile
(l/derived :profile st/state))
;; ---- Workspace refs
(def workspace-local
(l/derived :workspace-local st/state))
@ -79,6 +84,15 @@
(vec))))]
(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?
[id]
(letfn [(selector [state]
@ -120,3 +134,21 @@
(def current-transform
(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.custom-stroke :refer [shape-custom-stroke]]))
;; --- Circle Wrapper
;; --- Circle Wrapper for workspace
(declare circle-shape)
(mf/defc circle-wrapper
[{:keys [shape] :as props}]
(let [selected (mf/deref refs/selected-shapes)
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape))
on-mouse-down #(common/on-mouse-down % shape)
on-context-menu #(common/on-context-menu % shape)]
@ -32,6 +34,36 @@
:on-context-menu on-context-menu}
[:& 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
(mf/defc circle-shape

View file

@ -16,6 +16,7 @@
[uxbox.common.data :as d]
[uxbox.common.spec :as us]
[uxbox.main.data.workspace :as dw]
[uxbox.main.data.viewer :as dv]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.keyboard :as kbd]
@ -66,10 +67,24 @@
(st/emit! (dw/start-move-selected)))))))
;; --- Workspace context menu
(defn on-context-menu
[event shape]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [position (dom/get-client-position event)]
(st/emit!(dw/show-shape-context-menu {:position position
:shape shape}))))
(st/emit! (dw/show-shape-context-menu {:position position
: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 translate-to-frame)
;; ---- Frame Wrapper for workspace
(defn frame-wrapper-memo-equals?
[np op]
(let [n-shape (aget np "shape")
@ -114,6 +116,20 @@
{:shape (geom/transform-shape shape)
: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
[shape-wrapper]
(mf/fnc frame-shape

View file

@ -14,25 +14,60 @@
[uxbox.main.ui.shapes.common :as common]
[uxbox.util.interop :as itr]))
;; --- Icon Wrapper
;; --- Icon Wrapper for workspace
(declare icon-shape)
(mf/defc icon-wrapper
[{:keys [shape frame] :as props}]
(let [selected (mf/deref refs/selected-shapes)
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape))
on-mouse-down #(common/on-mouse-down % shape)]
[:g.shape {:class (when selected? "selected")
:on-mouse-down on-mouse-down}
[:& 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
(mf/defc icon-shape
[{:keys [shape] :as props}]
(let [{:keys [id x y width height metadata rotation content] :as shape} shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height metadata rotation content]} shape
transform (when (and rotation (pos? rotation))
(str/format "rotate(%s %s %s)"
rotation

View file

@ -17,13 +17,15 @@
[uxbox.util.interop :as itr]
[uxbox.util.geom.matrix :as gmt]))
;; --- Image Wrapper
;; --- Image Wrapper for workspace
(declare image-shape)
(mf/defc image-wrapper
[{:keys [shape] :as props}]
(let [selected (mf/deref refs/selected-shapes)
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape))
on-mouse-down (mf/use-callback
(mf/deps shape)
@ -38,18 +40,48 @@
:on-context-menu on-context-menu}
[:& 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
(mf/defc image-shape
[{:keys [shape] :as props}]
(let [{:keys [id x y width height rotation metadata]} shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height rotation metadata]} shape
transform (geom/transform-matrix shape)
uri (if (or (> (:thumb-width metadata) width)
(> (:thumb-height metadata) height))
(:thumb-uri metadata)
(:uri metadata))
props (-> (attrs/extract-style-attrs shape)
(itr/obj-assign!
#js {:x x

View file

@ -19,13 +19,15 @@
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
;; --- Path Wrapper
;; --- Path Wrapper for workspace
(declare path-shape)
(mf/defc path-wrapper
[{:keys [shape] :as props}]
(let [selected (mf/deref refs/selected-shapes)
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape))
on-mouse-down (mf/use-callback
(mf/deps shape)
@ -44,6 +46,35 @@
:on-context-menu on-context-menu}
[:& 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
(defn- render-path
@ -68,8 +99,11 @@
(recur buffer (inc index)))))))
(mf/defc path-shape
[{:keys [shape background?] :as props}]
(let [{:keys [id x y width height]} (geom/shape->rect-shape shape)
{::mf/wrap-props false}
[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)
pdata (render-path shape)
props (-> (attrs/extract-style-attrs shape)

View file

@ -17,17 +17,17 @@
[uxbox.util.interop :as itr]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
;; --- Rect Wrapper
(declare rect-shape)
;; --- Rect Wrapper for workspace
(mf/defc rect-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 % shape))
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down % shape))
on-context-menu (mf/use-callback
(mf/deps shape)
#(common/on-context-menu % shape))]
@ -35,6 +35,36 @@
:on-context-menu on-context-menu}
[:& 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
(mf/defc rect-shape

View file

@ -39,15 +39,17 @@
(dom/stop-propagation event)
(common/on-mouse-down event shape)))
;; --- Text Wrapper
;; --- Text Wrapper for workspace
(declare text-shape-html)
(declare text-shape-edit)
(declare text-shape)
(mf/defc text-wrapper
[{:keys [shape] :as props}]
(let [{:keys [id x1 y1 content group]} shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x1 y1 content group]} shape
selected (mf/deref refs/selected-shapes)
edition (mf/deref refs/selected-edition)
edition? (= edition id)
@ -72,6 +74,35 @@
[:& text-shape {:shape shape
: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
(defn- generate-root-styles
@ -342,8 +373,11 @@
(render-text-node root)))
(mf/defc text-shape
[{:keys [shape selected?] :as props}]
(let [{:keys [id x y width height rotation content]} shape]
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
shape (unchecked-get props "selected?")
{:keys [id x y width height rotation content]} shape]
[:foreignObject {:x x
:y y
:transform (geom/transform-matrix shape)

View file

@ -17,13 +17,15 @@
[uxbox.builtins.icons :as i]
[uxbox.common.exceptions :as ex]
[uxbox.main.data.viewer :as dv]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.components.dropdown :refer [dropdown]]
[uxbox.main.ui.hooks :as hooks]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.messages :refer [messages]]
[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.dom :as dom]
[uxbox.util.i18n :as i18n :refer [t tr]])
@ -46,9 +48,9 @@
[:span (t locale "viewer.frame-not-found")]]
:else
[:& frame-svg {:frame frame
:zoom zoom
:objects objects}])]))
[:& frame-viewer-svg {:frame frame
:zoom zoom
:objects objects}])]))
(mf/defc viewer-content
[{:keys [data local index] :as props}]
@ -56,6 +58,13 @@
[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
(fn [event]
(when (kbd/ctrl? event)
@ -65,7 +74,6 @@
(st/emit! dv/decrease-zoom)
(st/emit! dv/increase-zoom)))))
on-mount
(fn []
;; bind with passive=false to allow the event to be cancelled
@ -88,7 +96,7 @@
:fullscreen? fullscreen?
:local local
:index index}]
[:div.viewer-content
[:div.viewer-content {:on-click on-click}
(when (:show-thumbnails local)
[:& thumbnails-panel {:index index
:data data}])
@ -99,20 +107,14 @@
;; --- 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
[{:keys [page-id index token] :as props}]
(mf/use-effect
(mf/deps page-id token)
#(st/emit! (dv/initialize page-id token)))
(let [data (mf/deref viewer-data-ref)
local (mf/deref viewer-local-ref)]
(let [data (mf/deref refs/viewer-data-ref)
local (mf/deref refs/viewer-local-ref)]
(when data
[:& viewer-content {:index index
: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.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
[{:keys [page] :as props}]
@ -77,6 +99,8 @@
total (count frames)
on-click #(st/emit! dv/toggle-thumbnails-panel)
interactions-mode (:interactions-mode local)
locale (i18n/use-locale)
profile (mf/deref refs/profile)
@ -105,6 +129,7 @@
[:span.counters (str (inc index) " / " total)]]
[:div.options-zone
[:& interactions-menu {:interactions-mode interactions-mode}]
(when-not anonymous?
[:& share-link {:page (:page data)}])
(when-not anonymous?
@ -127,4 +152,3 @@
i/full-screen)]
]]))

View file

@ -15,7 +15,6 @@
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.common.data :as d]
[uxbox.common.pages :as cp]
[uxbox.main.store :as st]
[uxbox.main.data.viewer :as dv]
[uxbox.main.ui.components.dropdown :refer [dropdown']]
@ -78,37 +77,6 @@
[:div.thumbnails-list-inside {:style {:right (str (* @offset 152) "px")}}
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
[{:keys [on-toggle-expand on-close total] :as props}]
[:div.thumbnails-summary
@ -122,7 +90,7 @@
[:div.thumbnail-item {:on-click #(on-click % index)}
[:div.thumbnail-preview
{:class (classnames :selected selected?)}
[:& frame-svg {:frame frame :objects objects}]]
[:& exports/frame-svg {:frame frame :objects objects}]]
[:div.thumbnail-info
[: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.text :as text]
[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]]))
;; --- Options
@ -45,29 +47,43 @@
:image [:& image/options {:shape shape}]
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
{:wrap [mf/memo]}
[{:keys [page selected] :as props}]
(let [close #(st/emit! (udw/toggle-layout-flag :element-options))
selected (mf/deref refs/selected-shapes)]
[:div.element-options.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]]
[:& align-options]
[:div.tool-window-content
[:div.element-options
(if (= (count selected) 1)
[:& shape-options-wrapper {:shape-id (first selected)
:page-id (:id page)}]
[:& page/options {:page page}])]]]))
on-change-tab #(st/emit! (udw/set-options-mode %))
options-mode (mf/deref refs/options-mode)
selected (mf/deref refs/selected-shapes)
shape-id (first selected)
page-id (:id page)
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)]
[: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)
(gpt/point (math/neg (- pval cval)) 0)
(gpt/point 0 (math/neg (- pval cval))))]
;; TODO: Change so not apply the modifiers until blur
(st/emit! (udw/set-modifiers #{(:id shape)} {:displacement delta})
(udw/apply-modifiers #{(:id shape)}))))
@ -94,9 +94,7 @@
(:name 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 :horiz)} i/size-horiz]
]
[:span.orientation-icon {on-click #(on-orientation-clicked :horiz)} i/size-horiz]]
;; WIDTH & HEIGHT
[:div.row-flex

View file

@ -14,10 +14,61 @@
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.colorpicker :as cp]
[uxbox.main.ui.components.dropdown :refer [dropdown]]
[uxbox.util.data :refer [read-string]]
[uxbox.util.dom :as dom]
[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
;; (defn- on-change
@ -469,25 +520,25 @@
;; --- Interactions Menu
(def +initial-form+
{:trigger :click
:action :show})
(mf/defc interactions-menu
[{:keys [menu shape] :as props}]
#_(let [form (mf/use-state nil)
interactions (:interactions shape)]
[:div.element-set {:key (str (:id menu))}
[:div.element-set-title (:name menu)]
[:div.element-set-content
(if form
[:& interactions-form {:form form :shape shape}]
[:div
[:& interactions-list {:form form :shape shape}]
[:input.btn-primary.btn-small
{:value "New interaction"
:on-click #(reset! form +initial-form+)
:type "button"}]])]]))
;; (def +initial-form+
;; {:trigger :click
;; :action :show})
;;
;; (mf/defc interactions-menu
;; [{:keys [menu shape] :as props}]
;; #_(let [form (mf/use-state nil)
;; interactions (:interactions shape)]
;; [:div.element-set {:key (str (:id menu))}
;; [:div.element-set-title (:name menu)]
;; [:div.element-set-content
;; (if form
;; [:& interactions-form {:form form :shape shape}]
;; [:div
;; [:& interactions-list {:form form :shape shape}]
;; [:input.btn-primary.btn-small
;; {:value "New interaction"
;; :on-click #(reset! form +initial-form+)
;; :type "button"}]])]]))
;; --- Not implemented stuff

View file

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