Adds multiselection attributes

This commit is contained in:
alonso.torres 2020-10-26 16:35:16 +01:00 committed by Hirunatan
parent d6573c2bcc
commit b52289860f
23 changed files with 831 additions and 612 deletions

View file

@ -214,3 +214,36 @@
(concat new-children new-child-objects)
(concat updated-children updated-child-objects))))))))
(defn indexed-shapes
"Retrieves a list with the indexes for each element in the layer tree.
This will be used for shift+selection."
[objects]
(let [rec-index
(fn rec-index [cur-idx id]
(let [object (get objects id)
red-fn
(fn [cur-idx id]
(let [[prev-idx _] (first cur-idx)
prev-idx (or prev-idx 0)
cur-idx (conj cur-idx [(inc prev-idx) id])]
(rec-index cur-idx id)))]
(reduce red-fn cur-idx (reverse (:shapes object)))))]
(into {} (rec-index '() uuid/zero))))
(defn expand-region-selection
"Given a selection selects all the shapes between the first and last in
an indexed manner (shift selection)"
[objects selection]
(let [indexed-shapes (indexed-shapes objects)
filter-indexes (->> indexed-shapes
(filter (comp selection second))
(map first))
from (apply min filter-indexes)
to (apply max filter-indexes)]
(->> indexed-shapes
(filter (fn [[idx _]] (and (>= idx from) (<= idx to))))
(map second)
(into #{}))))

View file

@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" fill="none">
<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 width="40" height="40" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.75 20a18.75 18.75 0 10-37.5 0 18.75 18.75 0 0037.5 0zM20 40a20 20 0 100-40 20 20 0 000 40z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M27.46 20c0 .93-.73 1.68-1.64 1.68H11.64c-.9 0-1.64-.75-1.64-1.68 0-.93.73-1.68 1.64-1.68h14.18c.9 0 1.64.75 1.64 1.68z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.74 21.51c.81-.83.81-2.19 0-3.02l-5.5-5.62a1.71 1.71 0 010-2.38 1.61 1.61 0 012.32 0l7.83 8c.81.83.81 2.19 0 3.02l-7.83 8c-.64.65-1.67.65-2.32 0a1.71 1.71 0 010-2.38l5.5-5.62z"/>
</svg>

Before

Width:  |  Height:  |  Size: 542 B

After

Width:  |  Height:  |  Size: 628 B

View file

@ -1,3 +1,3 @@
<svg height="500" viewBox="0 0 500 500.00001" width="500" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="15.5" y="78" width="469" height="344" rx="50"/>
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.5 30V10A2.5 2.5 0 0035 7.5H5A2.5 2.5 0 002.5 10v20A2.5 2.5 0 005 32.5h30a2.5 2.5 0 002.5-2.5zM40 10a5 5 0 00-5-5H5a5 5 0 00-5 5v20a5 5 0 005 5h30a5 5 0 005-5V10z"/>
</svg>

Before

Width:  |  Height:  |  Size: 172 B

After

Width:  |  Height:  |  Size: 291 B

View file

@ -1,3 +1,3 @@
<svg height="500" viewBox="0 0 500 500.00001" width="500" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="78" y="15.5" width="344" height="469" rx="50"/>
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 2.5H10A2.5 2.5 0 007.5 5v30a2.5 2.5 0 002.5 2.5h20a2.5 2.5 0 002.5-2.5V5A2.5 2.5 0 0030 2.5zM10 0a5 5 0 00-5 5v30a5 5 0 005 5h20a5 5 0 005-5V5a5 5 0 00-5-5H10z"/>
</svg>

Before

Width:  |  Height:  |  Size: 172 B

After

Width:  |  Height:  |  Size: 289 B

View file

@ -96,6 +96,11 @@
position: relative;
align-items: center;
.color-text {
width: 3rem;
text-transform: uppercase;
}
.attributes-color-display {
display: flex;
}

View file

@ -571,14 +571,13 @@
svg {
cursor: pointer;
height: 20px;
stroke: $color-gray-40;
stroke-width: 30px;
fill: $color-gray-40;
width: 20px;
}
&:hover {
svg {
stroke: $color-gray-10;
fill: $color-gray-10;
}
}
}

View file

@ -20,7 +20,8 @@
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.util.router :as rt]
[app.common.uuid :as uuid]))
[app.common.uuid :as uuid]
[app.common.pages-helpers :as cph]))
;; --- Specs
@ -242,20 +243,17 @@
frames (get-in state [:viewer-data :frames])
share-token (get-in state [:viewer-data :share-token])
index (d/index-of-pred frames #(= (:id %) frame-id))]
(rx/of (rt/nav :viewer {:page-id page-id :file-id file-id} {:token share-token
(rx/of (rt/nav :viewer
{:page-id page-id
:file-id file-id}
{:token share-token
:index index}))))))
;; --- Shortcuts
(def shortcuts
{"+" #(st/emit! increase-zoom)
"-" #(st/emit! decrease-zoom)
"shift+0" #(st/emit! zoom-to-50)
"shift+1" #(st/emit! reset-zoom)
"shift+2" #(st/emit! zoom-to-200)
"left" #(st/emit! select-prev-frame)
"right" #(st/emit! select-next-frame)})
(defn set-current-frame [frame-id]
(ptk/reify ::current-frame
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:viewer-data :current-frame-id] frame-id))))
(defn deselect-all []
(ptk/reify ::deselect-all
@ -264,17 +262,49 @@
(assoc-in state [:viewer-local :selected] #{}))))
(defn select-shape
([id] (select-shape id false))
([id toggle?]
([id]
(ptk/reify ::select-shape
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:viewer-local :selected] #{id}))))))
;; TODO
(defn collapse-all []
(ptk/reify ::collapse-all))
(defn toggle-selection
[id]
(ptk/reify ::toggle-selection
ptk/UpdateEvent
(update [_ state]
(let [selected (get-in state [:viewer-local :selected])]
(cond-> state
(not (selected id)) (update-in [:viewer-local :selected] conj id)
(selected id) (update-in [:viewer-local :selected] disj id))))))
(defn shift-select-to
[id]
(ptk/reify ::shift-select-to
ptk/UpdateEvent
(update [_ state]
(let [objects (get-in state [:viewer-data :objects])
selection (-> state
(get-in [:viewer-local :selected] #{})
(conj id))]
(-> state
(assoc-in [:viewer-local :selected]
(cph/expand-region-selection objects selection)))))))
(defn select-all
[]
(ptk/reify ::shift-select-to
ptk/UpdateEvent
(update [_ state]
(let [objects (get-in state [:viewer-data :objects])
frame-id (get-in state [:viewer-data :current-frame-id])
selection (->> objects
(filter #(= (:frame-id (second %)) frame-id))
(map first)
(into #{frame-id}))]
(-> state
(assoc-in [:viewer-local :selected] selection))))))
(defn toggle-collapse [id]
(ptk/reify ::toggle-collapse
@ -288,3 +318,17 @@
ptk/UpdateEvent
(update [_ state]
(update-in state [:viewer-local :hover] (if hover? conj disj) id))))
;; --- Shortcuts
(def shortcuts
{"+" #(st/emit! increase-zoom)
"-" #(st/emit! decrease-zoom)
"ctrl+a" #(st/emit! (select-all))
"shift+0" #(st/emit! zoom-to-50)
"shift+1" #(st/emit! reset-zoom)
"shift+2" #(st/emit! zoom-to-200)
"left" #(st/emit! select-prev-frame)
"right" #(st/emit! select-next-frame)})

View file

@ -74,8 +74,12 @@
(logjs "state" @state))
(defn ^:export dump-objects []
(let [page-id (get @state :current-page-id)]
(logjs "state" (get-in @state [:workspace-data :pages-index page-id :objects]))))
(let [page-id (get @state :current-page-id)
objects (get-in @state [:workspace-data :pages-index page-id :objects])]
(logjs "index" (->> (cph/indexed-shapes objects)
(d/mapm (fn [k id] (get objects id)))
(map (fn [[idx obj]] (str idx " - " (:name obj))))))
(logjs "state" objects)))
(defn ^:export dump-object [name]
(let [page-id (get @state :current-page-id)]

View file

@ -150,10 +150,9 @@
(mf/defc debug-icons-preview
{::mf/wrap-props false}
[props]
[:section.debug-icons-preview {:style {:background-color "black"}}
[:section.debug-icons-preview
(for [[key val] (sort-by first (ns-publics 'app.main.ui.icons))]
(when (not= key 'debug-icons-preview)
[:div.icon-item {:key key
:style {:fill "white"}}
[:div.icon-item {:key key}
(deref val)
[:span (pr-str key)]]))])

View file

@ -26,10 +26,9 @@
[app.main.ui.keyboard :as kbd]
[app.main.ui.viewer.header :refer [header]]
[app.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
[app.main.ui.viewer.handoff.render :refer [render-frame-svg]]
[app.main.ui.viewer.handoff.layers-sidebar :refer [layers-sidebar]]
[app.main.ui.viewer.handoff.attributes-sidebar :refer [attributes-sidebar]])
[app.main.ui.viewer.handoff.left-sidebar :refer [left-sidebar]]
[app.main.ui.viewer.handoff.right-sidebar :refer [right-sidebar]])
(:import goog.events.EventType))
(defn handle-select-frame [frame]
@ -47,7 +46,8 @@
(mf/use-effect
(mf/deps index)
(fn []
(st/emit! (dv/select-shape (:id frame)))))
(st/emit! (dv/set-current-frame (:id frame))
(dv/select-shape (:id frame)))))
[:section.viewer-preview
(cond
@ -61,12 +61,12 @@
:else
[:*
[:& layers-sidebar {:frame frame}]
[:& left-sidebar {:frame frame}]
[:div.handoff-svg-wrapper {:on-click (handle-select-frame frame)}
[:& render-frame-svg {:frame-id (:id frame)
:zoom (:zoom local)
:objects objects}]]
[:& attributes-sidebar {:frame frame}]])]))
[:& right-sidebar {:frame frame}]])]))
(mf/defc handoff-content
[{:keys [data local index] :as props}]

View file

@ -1,440 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attrib-panel
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.config :as cfg]
[app.util.data :as d]
[app.util.dom :as dom]
[app.util.i18n :refer [locale t]]
[app.util.color :as uc]
[app.util.text :as ut]
[app.common.math :as mth]
[app.common.geom.shapes :as gsh]
[app.main.fonts :as fonts]
[app.main.ui.icons :as i]
[app.util.webapi :as wapi]
[app.main.ui.components.color-bullet :refer [color-bullet color-name]]))
(defn copy-cb [values properties & {:keys [to-prop format] :or {to-prop {}}}]
(fn [event]
(let [
;; We allow the :format and :to-prop to be a map for different properties
;; or just a value for a single property. This code transform a single
;; property to a uniform one
properties (if-not (coll? properties) [properties] properties)
format (if (not (map? format))
(into {} (map #(vector % format) properties))
format)
to-prop (if (not (map? to-prop))
(into {} (map #(vector % to-prop) properties))
to-prop)
default-format (fn [value] (str (mth/precision value 2) "px"))
format-property (fn [prop]
(let [css-prop (or (prop to-prop) (name prop))]
(str/fmt " %s: %s;" css-prop ((or (prop format) default-format) (prop values) values))))
text-props (->> properties
(remove #(let [value (get values %)]
(or (nil? value) (= value 0))))
(map format-property)
(str/join "\n"))
result (str/fmt "{\n%s\n}" text-props)]
(wapi/write-to-clipboard result))))
(mf/defc color-row [{:keys [color format on-copy on-change-format]}]
(let [locale (mf/deref locale)]
[:div.attributes-color-row
[:& color-bullet {:color color}]
(if (:gradient color)
[:& color-name {:color color}]
(case format
:rgba (let [[r g b a] (->> (uc/hex->rgba (:color color) (:opacity color)) (map #(mth/precision % 2)))]
[:div (str/fmt "%s, %s, %s, %s" r g b a)])
:hsla (let [[h s l a] (->> (uc/hex->hsla (:color color) (:opacity color)) (map #(mth/precision % 2)))]
[:div (str/fmt "%s, %s, %s, %s" h s l a)])
[:*
[:& color-name {:color color}]
(when-not (:gradient color) [:div (str (* 100 (:opacity color)) "%")])]))
(when-not (and on-change-format (:gradient color))
[:select {:on-change #(-> (dom/get-target-val %) keyword on-change-format)}
[:option {:value "hex"}
(t locale "handoff.attributes.color.hex")]
[:option {:value "rgba"}
(t locale "handoff.attributes.color.rgba")]
[:option {:value "hsla"}
(t locale "handoff.attributes.color.hsla")]])
(when on-copy
[:button.attributes-copy-button {:on-click on-copy} i/copy])]))
(mf/defc layout-panel
[{:keys [shape locale]}]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.layout")]
[:button.attributes-copy-button
{:on-click (copy-cb shape
[:width :height :x :y :rotation]
:to-prop {:x "left" :y "top" :rotation "transform"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)})}
i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.width")]
[:div.attributes-value (mth/precision (:width shape) 2) "px"]
[:button.attributes-copy-button
{:on-click (copy-cb shape :width)}
i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.height")]
[:div.attributes-value (mth/precision (:height shape) 2) "px"]
[:button.attributes-copy-button
{:on-click (copy-cb shape :height)}
i/copy]]
(when (not= (:x shape) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.left")]
[:div.attributes-value (mth/precision (:x shape) 2) "px"]
[:button.attributes-copy-button
{:on-click (copy-cb shape :x :to-prop "left")}
i/copy]])
(when (not= (:y shape) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.top")]
[:div.attributes-value (mth/precision (:y shape) 2) "px"]
[:button.attributes-copy-button
{:on-click (copy-cb shape :y :to-prop "top")}
i/copy]])
(when (not= (:rotation shape 0) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.rotation")]
[:div.attributes-value (mth/precision (:rotation shape) 2) "deg"]
[:button.attributes-copy-button
{:on-click (copy-cb shape
:rotation
:to-prop "transform"
:format #(str/fmt "rotate(%sdeg)" %))}
i/copy]])])
(mf/defc fill-panel
[{:keys [shape locale]}]
(let [color-format (mf/use-state :hex)
color {:color (:fill-color shape)
:opacity (:fill-opacity shape)
:gradient (:fill-color-gradient shape)
:id (:fill-ref-id shape)
:file-id (:fill-ref-file-id shape)}
handle-copy (copy-cb shape
[:fill-color :fill-color-gradient]
:to-prop "background"
:format #(uc/color->background color))]
(when (or (:color color) (:gradient color))
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.fill")]
[:button.attributes-copy-button
{:on-click handle-copy}
i/copy]]
[:& color-row {:color color
:format @color-format
:on-change-format #(reset! color-format %)
:on-copy handle-copy}]])))
(mf/defc stroke-panel
[{:keys [shape locale]}]
(let [color-format (mf/use-state :hex)
color {:color (:stroke-color shape)
:opacity (:stroke-opacity shape)
:gradient (:stroke-color-gradient shape)
:id (:stroke-color-ref-id shape)
:file-id (:stroke-color-file-id shape)}
handle-copy-stroke (copy-cb shape
:stroke-style
:to-prop "border"
:format #(let [width (:stroke-width %2)
style (name (:stroke-style %2))
color (uc/color->background color)]
(str/format "%spx %s %s" width style color)))]
(when (and (:stroke-style shape) (not= (:stroke-style shape) :none))
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.stroke")]
[:button.attributes-copy-button
{:on-click handle-copy-stroke} i/copy]]
[:& color-row {:color color
:format @color-format
:on-change-format #(reset! color-format %)
:on-copy (copy-cb shape
:stroke-color
:to-prop "border-color"
:format #(uc/color->background color))}]
[:div.attributes-stroke-row
[:div.attributes-label (t locale "handoff.attributes.stroke.width")]
[:div.attributes-value (:stroke-width shape) "px"]
[:div.attributes-value (->> shape :stroke-style name (str "handoff.attributes.stroke.style.") (t locale))]
[:div.attributes-label (->> shape :stroke-alignment name (str "handoff.attributes.stroke.alignment.") (t locale))]
[:button.attributes-copy-button
{:on-click handle-copy-stroke} i/copy]]])))
(defn shadow->css [shadow]
(let [{:keys [style offset-x offset-y blur spread]} shadow
css-color (uc/color->background (:color shadow))]
(str
(if (= style :inner-shadow) "inset " "")
(str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color))))
(mf/defc shadow-block [{:keys [shape locale shadow]}]
(let [color-format (mf/use-state :hex)]
[:div.attributes-shadow-block
[:div.attributes-shadow-row
[:div.attributes-label (->> shadow :style name (str "handoff.attributes.shadow.style.") (t locale))]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.offset-x")]
[:div.attributes-value (str (:offset-x shadow))]]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.offset-y")]
[:div.attributes-value (str (:offset-y shadow))]]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.blur")]
[:div.attributes-value (str (:blur shadow))]]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.spread")]
[:div.attributes-value (str (:spread shadow))]]
[:button.attributes-copy-button
{:on-click (copy-cb shadow
:style
:to-prop "box-shadow"
:format #(shadow->css shadow))}
i/copy]]
[:& color-row {:color (:color shadow)
:format @color-format
:on-change-format #(reset! color-format %)}]]))
(mf/defc shadow-panel [{:keys [shape locale]}]
(when (seq (:shadow shape))
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.shadow")]
[:button.attributes-copy-button
{:on-click (copy-cb shape
:shadow
:to-prop "box-shadow"
:format #(str/join ", " (map shadow->css (:shadow shape))))}
i/copy]]
(for [shadow (:shadow shape)]
[:& shadow-block {:shape shape
:locale locale
:shadow shadow}])]))
(mf/defc blur-panel [{:keys [shape locale]}]
(let [handle-copy
(copy-cb shape
:blur
:to-prop "filter"
:format #(str/fmt "blur(%spx)" %))]
(when (:blur shape)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.blur")]
[:button.attributes-copy-button {:on-click handle-copy} i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.blur.value")]
[:div.attributes-value (-> shape :blur :value) "px"]
[:button.attributes-copy-button {:on-click handle-copy} i/copy]]])))
(mf/defc image-panel [{:keys [shape locale]}]
[:div.attributes-block
[:div.attributes-image-row
[:div.attributes-image
[:img {:src (cfg/resolve-media-path (-> shape :metadata :path))}]]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.image.width")]
[:div.attributes-value (-> shape :metadata :width) "px"]
[:button.attributes-copy-button {:on-click (copy-cb shape :width)} i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.image.height")]
[:div.attributes-value (-> shape :metadata :height) "px"]
[:button.attributes-copy-button {:on-click (copy-cb shape :height)} i/copy]]
(let [filename (last (str/split (-> shape :metadata :path) "/"))]
[:a.download-button {:target "_blank"
:download filename
:href (cfg/resolve-media-path (-> shape :metadata :path))}
(t locale "handoff.attributes.image.download")])])
(mf/defc text-block [{:keys [shape locale text style full-style]}]
(let [color-format (mf/use-state :hex)
color {:color (:fill-color style)
:opacity (:fill-opacity style)
:gradient (:fill-color-gradient style)
:id (:fill-color-ref-id style)
:file-id (:fill-color-ref-file-id style)}
properties [:fill-color
:fill-color-gradient
:font-family
:font-style
:font-size
:line-height
:letter-spacing
:text-decoration
:text-transform]
format {:font-family identity
:font-style identity
:font-size #(str % "px")
:line-height #(str % "px")
:letter-spacing #(str % "px")
:text-decoration name
:text-transform name
:fill-color #(uc/color->background color)
:fill-color-gradient #(uc/color->background color)}
to-prop {:fill-color "color"
:fill-color-gradient "color"}]
[:div.attributes-text-block
[:div.attributes-typography-row
[:div.typography-sample
{:style {:font-family (:font-family full-style)
:font-weight (:font-weight full-style)
:font-style (:font-style full-style)}}
(t locale "workspace.assets.typography.sample")]
[:button.attributes-copy-button
{:on-click (copy-cb style properties :to-prop to-prop :format format)} i/copy]]
[:div.attributes-content-row
[:pre.attributes-content (str/trim text)]
[:button.attributes-copy-button
{:on-click #(wapi/write-to-clipboard (str/trim text))}
i/copy]]
(when (or (:fill-color style) (:fill-color-gradient style))
(let [color {:color (:fill-color style)
:opacity (:fill-opacity style)
:gradient (:fill-color-gradient style)
:id (:fill-ref-id style)
:file-id (:fill-ref-file-id style)}]
[:& color-row {:format @color-format
:on-change-format #(reset! color-format %)
:color color
:on-copy (copy-cb style [:fill-color :fill-color-gradient] :to-prop to-prop :format format)}]))
(when (:font-id style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.font-family")]
[:div.attributes-value (-> style :font-id fonts/get-font-data :name)]
[:button.attributes-copy-button {:on-click (copy-cb style :font-family :format identity)} i/copy]])
(when (:font-style style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.font-style")]
[:div.attributes-value (str (:font-style style))]
[:button.attributes-copy-button {:on-click (copy-cb style :font-style :format identity)} i/copy]])
(when (:font-size style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.font-size")]
[:div.attributes-value (str (:font-size style)) "px"]
[:button.attributes-copy-button {:on-click (copy-cb style :font-size :format #(str % "px"))} i/copy]])
(when (:line-height style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.line-height")]
[:div.attributes-value (str (:line-height style)) "px"]
[:button.attributes-copy-button {:on-click (copy-cb style :line-height :format #(str % "px"))} i/copy]])
(when (:letter-spacing style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.letter-spacing")]
[:div.attributes-value (str (:letter-spacing style)) "px"]
[:button.attributes-copy-button {:on-click (copy-cb style :letter-spacing :format #(str % "px"))} i/copy]])
(when (:text-decoration style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.text-decoration")]
[:div.attributes-value (->> style :text-decoration (str "handoff.attributes.typography.text-decoration.") (t locale))]
[:button.attributes-copy-button {:on-click (copy-cb style :text-decoration :format name)} i/copy]])
(when (:text-transform style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.text-transform")]
[:div.attributes-value (->> style :text-transform (str "handoff.attributes.typography.text-transform.") (t locale))]
[:button.attributes-copy-button {:on-click (copy-cb style :text-transform :format name)} i/copy]])]))
(mf/defc typography-panel [{:keys [shape locale]}]
(let [font (ut/search-text-attrs (:content shape)
(keys ut/default-text-attrs))
style-text-blocks (->> (keys ut/default-text-attrs)
(ut/parse-style-text-blocks (:content shape))
(remove (fn [[style text]] (str/empty? (str/trim text))))
(mapv (fn [[style text]] (vector (merge ut/default-text-attrs style) text))))
font (merge ut/default-text-attrs font)]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.typography")]]
(for [[idx [full-style text]] (map-indexed vector style-text-blocks)]
(let [previus-style (first (nth style-text-blocks (dec idx) nil))
style (d/remove-equal-values full-style previus-style)
;; If the color is set we need to add opacity otherwise the display will not work
style (cond-> style
(:fill-color style)
(assoc :fill-opacity (:fill-opacity full-style)))]
[:& text-block {:shape shape
:locale locale
:full-style full-style
:style style
:text text}]))]))
(mf/defc attrib-panel [{:keys [shape frame options]}]
(let [locale (mf/deref locale)]
[:div.element-options
(for [option options]
[:>
(case option
:layout layout-panel
:fill fill-panel
:stroke stroke-panel
:shadow shadow-panel
:blur blur-panel
:image image-panel
:typography typography-panel)
{:shape (gsh/translate-to-frame shape frame)
:frame frame
:locale locale}])]))

View file

@ -0,0 +1,43 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes
(:require
[rumext.alpha :as mf]
[app.util.i18n :as i18n]
[app.common.geom.shapes :as gsh]
[app.main.ui.viewer.handoff.attributes.layout :refer [layout-panel]]
[app.main.ui.viewer.handoff.attributes.fill :refer [fill-panel]]
[app.main.ui.viewer.handoff.attributes.stroke :refer [stroke-panel]]
[app.main.ui.viewer.handoff.attributes.shadow :refer [shadow-panel]]
[app.main.ui.viewer.handoff.attributes.blur :refer [blur-panel]]
[app.main.ui.viewer.handoff.attributes.image :refer [image-panel]]
[app.main.ui.viewer.handoff.attributes.text :refer [text-panel]]))
(mf/defc attributes
[{:keys [shapes frame options]}]
(let [locale (mf/deref i18n/locale)
shapes (->> shapes
(map #(gsh/translate-to-frame % frame)))
shape (first shapes)]
[:div.element-options
(for [option options]
[:> (case option
:layout layout-panel
:fill fill-panel
:stroke stroke-panel
:shadow shadow-panel
:blur blur-panel
:image image-panel
:text text-panel)
{:shapes shapes
:frame frame
:locale locale}])]))

View file

@ -0,0 +1,41 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes.blur
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.util.i18n :refer [t]]
[app.main.ui.icons :as i]
[app.main.ui.viewer.handoff.attributes.common :refer [copy-cb]]))
(defn has-blur? [shape]
(:blur shape))
(defn copy-blur [shape]
(copy-cb shape
:blur
:to-prop "filter"
:format #(str/fmt "blur(%spx)" (:value %))))
(mf/defc blur-panel [{:keys [shapes locale]}]
(let [shapes (->> shapes (filter has-blur?))
handle-copy (when (= (count shapes) 1) (copy-blur (first shapes)))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.blur")]
(when handle-copy
[:button.attributes-copy-button {:on-click handle-copy} i/copy])]
(for [shape shapes]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.blur.value")]
[:div.attributes-value (-> shape :blur :value) "px"]
[:button.attributes-copy-button {:on-click (copy-blur shape)} i/copy]])])))

View file

@ -0,0 +1,81 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes.common
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.util.dom :as dom]
[app.util.i18n :refer [t] :as i18n]
[app.util.color :as uc]
[app.common.math :as mth]
[app.main.ui.icons :as i]
[app.util.webapi :as wapi]
[app.main.ui.components.color-bullet :refer [color-bullet color-name]]))
(defn copy-cb [values properties & {:keys [to-prop format] :or {to-prop {}}}]
(fn [event]
(let [
;; We allow the :format and :to-prop to be a map for different properties
;; or just a value for a single property. This code transform a single
;; property to a uniform one
properties (if-not (coll? properties) [properties] properties)
format (if (not (map? format))
(into {} (map #(vector % format) properties))
format)
to-prop (if (not (map? to-prop))
(into {} (map #(vector % to-prop) properties))
to-prop)
default-format (fn [value] (str (mth/precision value 2) "px"))
format-property (fn [prop]
(let [css-prop (or (prop to-prop) (name prop))]
(str/fmt " %s: %s;" css-prop ((or (prop format) default-format) (prop values) values))))
text-props (->> properties
(remove #(let [value (get values %)]
(or (nil? value) (= value 0))))
(map format-property)
(str/join "\n"))
result (str/fmt "{\n%s\n}" text-props)]
(wapi/write-to-clipboard result))))
(mf/defc color-row [{:keys [color format on-copy on-change-format]}]
(let [locale (mf/deref i18n/locale)]
[:div.attributes-color-row
[:& color-bullet {:color color}]
(if (:gradient color)
[:& color-name {:color color}]
(case format
:rgba (let [[r g b a] (->> (uc/hex->rgba (:color color) (:opacity color)) (map #(mth/precision % 2)))]
[:div (str/fmt "%s, %s, %s, %s" r g b a)])
:hsla (let [[h s l a] (->> (uc/hex->hsla (:color color) (:opacity color)) (map #(mth/precision % 2)))]
[:div (str/fmt "%s, %s, %s, %s" h s l a)])
[:*
[:& color-name {:color color}]
(when-not (:gradient color) [:div (str (* 100 (:opacity color)) "%")])]))
(when-not (and on-change-format (:gradient color))
[:select {:on-change #(-> (dom/get-target-val %) keyword on-change-format)}
[:option {:value "hex"}
(t locale "handoff.attributes.color.hex")]
[:option {:value "rgba"}
(t locale "handoff.attributes.color.rgba")]
[:option {:value "hsla"}
(t locale "handoff.attributes.color.hsla")]])
(when on-copy
[:button.attributes-copy-button {:on-click on-copy} i/copy])]))

View file

@ -0,0 +1,67 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes.fill
(:require
[rumext.alpha :as mf]
[app.util.i18n :refer [t]]
[app.util.color :as uc]
[app.main.ui.icons :as i]
[app.main.ui.viewer.handoff.attributes.common :refer [copy-cb color-row]]))
(def fill-attributes [:fill-color :fill-color-gradient])
(defn shape->color [shape]
{:color (:fill-color shape)
:opacity (:fill-opacity shape)
:gradient (:fill-color-gradient shape)
:id (:fill-ref-id shape)
:file-id (:fill-ref-file-id shape)})
(defn has-color? [shape]
(and
(not (contains? #{:image :text :group} (:type shape)))
(or (:fill-color shape)
(:fill-color-gradient shape))))
(mf/defc fill-block [{:keys [shape locale]}]
(let [color-format (mf/use-state :hex)
color (shape->color shape)
handle-copy (copy-cb shape
fill-attributes
:to-prop "background"
:format #(uc/color->background color))]
[:& color-row {:color color
:format @color-format
:on-change-format #(reset! color-format %)
:on-copy handle-copy}]))
(mf/defc fill-panel
[{:keys [shapes locale]}]
(let [shapes (->> shapes (filter has-color?))
handle-copy (when (= (count shapes) 1)
(copy-cb (first shapes)
fill-attributes
:to-prop "background"
:format #(-> shapes first shape->color uc/color->background)))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.fill")]
(when handle-copy
[:button.attributes-copy-button
{:on-click handle-copy}
i/copy])]
(for [shape shapes]
[:& fill-block {:key (str "fill-block-" (:id shape))
:shape shape
:locale locale}])])))

View file

@ -0,0 +1,44 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes.image
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.config :as cfg]
[app.util.i18n :refer [t]]
[app.main.ui.icons :as i]
[app.main.ui.viewer.handoff.attributes.common :refer [copy-cb]]))
(defn has-image? [shape]
(and (= (:type shape) :image)))
(mf/defc image-panel [{:keys [shapes locale]}]
(let [shapes (->> shapes (filter has-image?))]
(for [shape shapes]
[:div.attributes-block {:key (str "image-" (:id shape))}
[:div.attributes-image-row
[:div.attributes-image
[:img {:src (cfg/resolve-media-path (-> shape :metadata :path))}]]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.image.width")]
[:div.attributes-value (-> shape :metadata :width) "px"]
[:button.attributes-copy-button {:on-click (copy-cb shape :width)} i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.image.height")]
[:div.attributes-value (-> shape :metadata :height) "px"]
[:button.attributes-copy-button {:on-click (copy-cb shape :height)} i/copy]]
(let [filename (last (str/split (-> shape :metadata :path) "/"))]
[:a.download-button {:target "_blank"
:download filename
:href (cfg/resolve-media-path (-> shape :metadata :path))}
(t locale "handoff.attributes.image.download")])])))

View file

@ -10,100 +10,75 @@
(ns app.main.ui.viewer.handoff.attributes.layout
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.util.i18n :refer [t]]
[app.common.math :as mth]
[app.main.ui.icons :as i]
[app.main.ui.components.color-bullet :refer [color-bullet color-name]]))
[app.main.ui.viewer.handoff.attributes.common :refer [copy-cb]]))
(defn copy-layout [shape]
(copy-cb shape
[:width :height :x :y :rotation]
:to-prop {:x "left" :y "top" :rotation "transform"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)}))
(mf/defc layout-panel [{:keys [shapes]}]
(mf/defc layout-block
[{:keys [shape locale]}]
[:*
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.width")]
[:div.attributes-value (mth/precision (:width shape) 2) "px"]
[:button.attributes-copy-button
{:on-click (copy-cb shape :width)}
i/copy]]
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.height")]
[:div.attributes-value (mth/precision (:height shape) 2) "px"]
[:button.attributes-copy-button
{:on-click (copy-cb shape :height)}
i/copy]]
(when (not= (:x shape) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.left")]
[:div.attributes-value (mth/precision (:x shape) 2) "px"]
[:button.attributes-copy-button
{:on-click (copy-cb shape :x :to-prop "left")}
i/copy]])
(when (not= (:y shape) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.top")]
[:div.attributes-value (mth/precision (:y shape) 2) "px"]
[:button.attributes-copy-button
{:on-click (copy-cb shape :y :to-prop "top")}
i/copy]])
(when (not= (:rotation shape 0) 0)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.layout.rotation")]
[:div.attributes-value (mth/precision (:rotation shape) 2) "deg"]
[:button.attributes-copy-button
{:on-click (copy-cb shape
:rotation
:to-prop "transform"
:format #(str/fmt "rotate(%sdeg)" %))}
i/copy]])])
(mf/defc layout-panel
[{:keys [shapes locale]}]
(let [handle-copy (when (= (count shapes) 1)
(copy-layout (first shapes)))]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Layout"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-block-title-text (t locale "handoff.attributes.layout")]
(when handle-copy
[:button.attributes-copy-button
{:on-click handle-copy}
i/copy])]
[:div.attributes-unit-row
[:div.attributes-label "Width"]
[:div.attributes-value "100px"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label "Height"]
[:div.attributes-value "100px"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label "Top"]
[:div.attributes-value "100px"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-unit-row
[:div.attributes-label "Left"]
[:div.attributes-value "100px"]
[:button.attributes-copy-button i/copy]]]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Fill"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-shadow-row
[:div.attributes-label "Drop"]
[:div.attributes-shadow
[:div.attributes-label "X"]
[:div.attributes-value "4"]]
[:div.attributes-shadow
[:div.attributes-label "Y"]
[:div.attributes-value "4"]]
[:div.attributes-shadow
[:div.attributes-label "B"]
[:div.attributes-value "0"]]
[:div.attributes-shadow
[:div.attributes-label "B"]
[:div.attributes-value "0"]]
[:button.attributes-copy-button i/copy]]
[:div.attributes-color-row
[:& color-bullet {:color {:color "#000000" :opacity 0.5}}]
[:*
[:div "#000000"]
[:div "100%"]]
[:select
[:option "Hex"]
[:option "RGBA"]
[:option "HSLA"]]
[:button.attributes-copy-button i/copy]]
[:div.attributes-stroke-row
[:div.attributes-label "Width"]
[:div.attributes-value "1px"]
[:div.attributes-value "Solid"]
[:div.attributes-label "Center"]
[:button.attributes-copy-button i/copy]]]
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Content"]
[:button.attributes-copy-button i/copy]]
[:div.attributes-content-row
[:div.attributes-content
"Hi, how are you"]
[:button.attributes-copy-button i/copy]]]
[:div.attributes-block
[:div.attributes-image-row
[:div.attributes-image
#_[:img {:src "https://www.publico.es/tremending/wp-content/uploads/2019/05/Cxagv.jpg"}]
#_[:img {:src "https://i.blogs.es/3861b2/grumpy-cat/1366_2000.png"}]
[:img {:src "https://abs.twimg.com/favicons/twitter.ico"}]
]]
[:button.download-button "Dowload source image"]]
])
(for [shape shapes]
[:& layout-block {:shape shape
:locale locale}])]))

View file

@ -0,0 +1,79 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes.shadow
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.util.i18n :refer [t]]
[app.util.color :as uc]
[app.main.ui.icons :as i]
[app.main.ui.viewer.handoff.attributes.common :refer [copy-cb color-row]]))
(defn has-shadow? [shape]
(:shadow shape))
(defn shadow->css [shadow]
(let [{:keys [style offset-x offset-y blur spread]} shadow
css-color (uc/color->background (:color shadow))]
(str
(if (= style :inner-shadow) "inset " "")
(str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color))))
(mf/defc shadow-block [{:keys [shape locale shadow]}]
(let [color-format (mf/use-state :hex)]
[:div.attributes-shadow-block
[:div.attributes-shadow-row
[:div.attributes-label (->> shadow :style name (str "handoff.attributes.shadow.style.") (t locale))]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.offset-x")]
[:div.attributes-value (str (:offset-x shadow))]]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.offset-y")]
[:div.attributes-value (str (:offset-y shadow))]]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.blur")]
[:div.attributes-value (str (:blur shadow))]]
[:div.attributes-shadow
[:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.spread")]
[:div.attributes-value (str (:spread shadow))]]
[:button.attributes-copy-button
{:on-click (copy-cb shadow
:style
:to-prop "box-shadow"
:format #(shadow->css shadow))}
i/copy]]
[:& color-row {:color (:color shadow)
:format @color-format
:on-change-format #(reset! color-format %)}]]))
(mf/defc shadow-panel [{:keys [shapes locale]}]
(let [shapes (->> shapes (filter has-shadow?))
handle-copy-shadow (when (= (count shapes) 1)
(copy-cb (first shapes)
:shadow
:to-prop "box-shadow"
:format #(str/join ", " (map shadow->css (:shadow (first shapes))))))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.shadow")]
(when handle-copy-shadow
[:button.attributes-copy-button {:on-click handle-copy-shadow} i/copy])]
(for [shape shapes]
(for [shadow (:shadow shape)]
[:& shadow-block {:shape shape
:locale locale
:shadow shadow}]))])))

View file

@ -0,0 +1,83 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes.stroke
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.util.i18n :refer [t]]
[app.util.color :as uc]
[app.main.ui.icons :as i]
[app.main.ui.viewer.handoff.attributes.common :refer [copy-cb color-row]]))
(defn shape->color [shape]
{:color (:stroke-color shape)
:opacity (:stroke-opacity shape)
:gradient (:stroke-color-gradient shape)
:id (:stroke-ref-id shape)
:file-id (:stroke-ref-file-id shape)})
(defn format-stroke [shape]
(let [width (:stroke-width shape)
style (name (:stroke-style shape))
color (-> shape shape->color uc/color->background)]
(str/format "%spx %s %s" width style color)))
(defn has-stroke? [shape]
(and (:stroke-style shape)
(not= (:stroke-style shape) :none)))
(mf/defc stroke-block
[{:keys [shape locale]}]
(let [color-format (mf/use-state :hex)
color (shape->color shape)
handle-copy-stroke (copy-cb shape
:stroke-style
:to-prop "border"
:format #(format-stroke shape))
handle-copy-color (copy-cb shape
:stroke-color
:to-prop "border-color"
:format #(uc/color->background color))]
[:*
[:& color-row {:color color
:format @color-format
:on-change-format #(reset! color-format %)
:on-copy handle-copy-color}]
[:div.attributes-stroke-row
[:div.attributes-label (t locale "handoff.attributes.stroke.width")]
[:div.attributes-value (:stroke-width shape) "px"]
[:div.attributes-value (->> shape :stroke-style name (str "handoff.attributes.stroke.style.") (t locale))]
[:div.attributes-label (->> shape :stroke-alignment name (str "handoff.attributes.stroke.alignment.") (t locale))]
[:button.attributes-copy-button {:on-click handle-copy-stroke} i/copy]]]))
(mf/defc stroke-panel
[{:keys [shapes locale]}]
(let [shapes (->> shapes (filter has-stroke?))
handle-copy (when (= (count shapes) 1)
(copy-cb (first shapes)
:stroke-style
:to-prop "border"
:format #(format-stroke (first shapes))))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.stroke")]
(when handle-copy
[:button.attributes-copy-button
{:on-click handle-copy} i/copy])]
(for [shape shapes]
[:& stroke-block {:key (str "stroke-color-" (:id shape))
:shape shape
:locale locale}])])))

View file

@ -0,0 +1,164 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes.text
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.util.data :as d]
[app.util.i18n :refer [t]]
[app.util.color :as uc]
[app.util.text :as ut]
[app.main.fonts :as fonts]
[app.main.ui.icons :as i]
[app.util.webapi :as wapi]
[app.main.ui.viewer.handoff.attributes.common :refer [copy-cb color-row]]))
(defn has-text? [shape]
(:content shape))
(def properties [:fill-color
:fill-color-gradient
:font-family
:font-style
:font-size
:line-height
:letter-spacing
:text-decoration
:text-transform])
(defn shape->color [shape]
{:color (:fill-color shape)
:opacity (:fill-opacity shape)
:gradient (:fill-color-gradient shape)
:id (:fill-ref-id shape)
:file-id (:fill-ref-file-id shape)})
(defn format-style [color]
{:font-family #(str "'" % "'")
:font-style #(str "'" % "'")
:font-size #(str % "px")
:line-height #(str % "px")
:letter-spacing #(str % "px")
:text-decoration name
:text-transform name
:fill-color #(uc/color->background color)
:fill-color-gradient #(uc/color->background color)})
(mf/defc typography-block [{:keys [shape locale text style full-style]}]
(let [color-format (mf/use-state :hex)
color (shape->color style)
to-prop {:fill-color "color"
:fill-color-gradient "color"}]
[:div.attributes-text-block
[:div.attributes-typography-row
[:div.typography-sample
{:style {:font-family (:font-family full-style)
:font-weight (:font-weight full-style)
:font-style (:font-style full-style)}}
(t locale "workspace.assets.typography.sample")]
[:button.attributes-copy-button
{:on-click (copy-cb style properties
:to-prop to-prop
:format (format-style color))}
i/copy]]
[:div.attributes-content-row
[:pre.attributes-content (str/trim text)]
[:button.attributes-copy-button
{:on-click #(wapi/write-to-clipboard (str/trim text))}
i/copy]]
(when (or (:fill-color style) (:fill-color-gradient style))
[:& color-row {:format @color-format
:on-change-format #(reset! color-format %)
:color (shape->color style)
:on-copy (copy-cb style
[:fill-color :fill-color-gradient]
:to-prop to-prop
:format (format-style color))}])
(when (:font-id style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.font-family")]
[:div.attributes-value (-> style :font-id fonts/get-font-data :name)]
[:button.attributes-copy-button {:on-click (copy-cb style :font-family :format identity)} i/copy]])
(when (:font-style style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.font-style")]
[:div.attributes-value (str (:font-style style))]
[:button.attributes-copy-button {:on-click (copy-cb style :font-style :format identity)} i/copy]])
(when (:font-size style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.font-size")]
[:div.attributes-value (str (:font-size style)) "px"]
[:button.attributes-copy-button {:on-click (copy-cb style :font-size :format #(str % "px"))} i/copy]])
(when (:line-height style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.line-height")]
[:div.attributes-value (str (:line-height style)) "px"]
[:button.attributes-copy-button {:on-click (copy-cb style :line-height :format #(str % "px"))} i/copy]])
(when (:letter-spacing style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.letter-spacing")]
[:div.attributes-value (str (:letter-spacing style)) "px"]
[:button.attributes-copy-button {:on-click (copy-cb style :letter-spacing :format #(str % "px"))} i/copy]])
(when (:text-decoration style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.text-decoration")]
[:div.attributes-value (->> style :text-decoration (str "handoff.attributes.typography.text-decoration.") (t locale))]
[:button.attributes-copy-button {:on-click (copy-cb style :text-decoration :format name)} i/copy]])
(when (:text-transform style)
[:div.attributes-unit-row
[:div.attributes-label (t locale "handoff.attributes.typography.text-transform")]
[:div.attributes-value (->> style :text-transform (str "handoff.attributes.typography.text-transform.") (t locale))]
[:button.attributes-copy-button {:on-click (copy-cb style :text-transform :format name)} i/copy]])]))
(mf/defc text-block [{:keys [shape locale]}]
(let [font (ut/search-text-attrs (:content shape)
(keys ut/default-text-attrs))
style-text-blocks (->> (keys ut/default-text-attrs)
(ut/parse-style-text-blocks (:content shape))
(remove (fn [[style text]] (str/empty? (str/trim text))))
(mapv (fn [[style text]] (vector (merge ut/default-text-attrs style) text))))
font (merge ut/default-text-attrs font)]
(for [[idx [full-style text]] (map-indexed vector style-text-blocks)]
(let [previus-style (first (nth style-text-blocks (dec idx) nil))
style (d/remove-equal-values full-style previus-style)
;; If the color is set we need to add opacity otherwise the display will not work
style (cond-> style
(:fill-color style)
(assoc :fill-opacity (:fill-opacity full-style)))]
[:& typography-block {:shape shape
:locale locale
:full-style full-style
:style style
:text text}]))))
(mf/defc text-panel [{:keys [shapes locale]}]
(let [shapes (->> shapes (filter has-text?))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (t locale "handoff.attributes.typography")]]
(for [shape shapes]
[:& text-block {:shape shape
:locale locale}])])))

View file

@ -7,7 +7,7 @@
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.layers-sidebar
(ns app.main.ui.viewer.handoff.left-sidebar
(:require
[rumext.alpha :as mf]
[okulary.core :as l]
@ -45,29 +45,22 @@
toggle-collapse
(fn [event]
(dom/stop-propagation event)
(if (and expanded? (kbd/shift? event))
(st/emit! (dv/collapse-all))
(st/emit! (dv/toggle-collapse id))))
(st/emit! (dv/toggle-collapse id)))
select-shape
(fn [event]
(dom/prevent-default event)
(let [id (:id item)]
(st/emit! (dv/select-shape id))
#_(cond
(or (:blocked item)
(:hidden item))
nil
(cond
(.-ctrlKey event)
(st/emit! (dv/toggle-selection id))
(.-shiftKey event)
(st/emit! (dv/select-shape id true))
(st/emit! (dv/shift-select-to id))
(> (count selected) 1)
(st/emit! (dv/deselect-all)
(dv/select-shape id))
:else
(st/emit! (dv/deselect-all)
(dv/select-shape id)))))
(st/emit! (dv/select-shape id)))
))
]
(mf/use-effect
@ -105,7 +98,7 @@
:objects objects
:key (:id item)}]))])]))
(mf/defc layers-sidebar [{:keys [frame]}]
(mf/defc left-sidebar [{:keys [frame]}]
(let [page (mf/deref page-ref)
selected (mf/deref selected-shapes)
objects (:objects page)]

View file

@ -44,10 +44,17 @@
(st/emit! (dv/hover-shape id hover?)))))
(defn select-shape [{:keys [type id]}]
#(when-not (#{:group :frame} type)
(dom/prevent-default %)
(dom/stop-propagation %)
(st/emit! (dv/select-shape id))))
(fn [event]
(when-not (#{:group :frame} type)
(do
(dom/stop-propagation event)
(dom/prevent-default event)
(cond
(.-shiftKey event)
(st/emit! (dv/toggle-selection id))
:else
(st/emit! (dv/select-shape id)))))))
(defn shape-wrapper-factory
[component]

View file

@ -7,7 +7,7 @@
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.ui.viewer.handoff.attributes-sidebar
(ns app.main.ui.viewer.handoff.right-sidebar
(:require
[rumext.alpha :as mf]
[okulary.core :as l]
@ -15,7 +15,7 @@
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
[app.main.ui.viewer.handoff.attrib-panel :refer [attrib-panel]]
[app.main.ui.viewer.handoff.attributes :refer [attributes]]
[app.main.ui.workspace.sidebar.layers :refer [element-icon]]))
(defn make-selected-shapes-iref
@ -28,14 +28,12 @@
(mapv resolve-shape selected)))]
#(l/derived selected->shapes st/state)))
(mf/defc info-panel [{:keys [frame shapes]}]
(if (> (count shapes) 1)
;; TODO:Multiple selection
nil
;; Single shape
(when-let [shape (first shapes)]
(let [options
(case (:type shape)
(mf/defc attributes-panel [{:keys [frame shapes]}]
(let [type (if (= (count shapes) 1)
(-> shapes first :type)
:multiple)]
(let [options (case type
:multiple [:fill :stroke :image :text :shadow :blur]
:frame [:layout :fill]
:group [:layout]
:rect [:layout :fill :stroke :shadow :blur]
@ -43,15 +41,15 @@
:path [:layout :fill :stroke :shadow :blur]
:curve [:layout :fill :stroke :shadow :blur]
:image [:image :layout :shadow :blur]
:text [:layout :typography :shadow :blur])]
[:& attrib-panel {:frame frame
:shape shape
:options options}]))))
:text [:layout :text :shadow :blur])]
[:& attributes {:frame frame
:shapes shapes
:options options}])))
(mf/defc code-panel []
[:div.element-options])
(mf/defc attributes-sidebar [{:keys [frame]}]
(mf/defc right-sidebar [{:keys [frame]}]
(let [locale (mf/deref i18n/locale)
section (mf/use-state :info #_:code)
selected-ref (mf/use-memo (make-selected-shapes-iref))
@ -74,7 +72,7 @@
[:& tab-container {:on-change-tab #(reset! section %)
:selected @section}
[:& tab-element {:id :info :title (t locale "handoff.tabs.info")}
[:& info-panel {:frame frame
[:& attributes-panel {:frame frame
:shapes shapes}]]
[:& tab-element {:id :code :title (t locale "handoff.tabs.code")}