mirror of
https://github.com/penpot/penpot.git
synced 2025-01-21 06:02:32 -05:00
Merge pull request #224 from tokens-studio/ux-context-menu
Ux context menu
This commit is contained in:
commit
ab72bdf09c
14 changed files with 915 additions and 772 deletions
|
@ -5,7 +5,9 @@
|
|||
"author": "Kaleidos INC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.2.2",
|
||||
"browserslist": ["defaults"],
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
[app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]]
|
||||
[app.main.ui.workspace.sidebar.collapsable-button :refer [collapsed-button]]
|
||||
[app.main.ui.workspace.sidebar.history :refer [history-toolbox]]
|
||||
[app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]]
|
||||
[app.main.ui.workspace.tokens.modals]
|
||||
[app.main.ui.workspace.viewport :refer [viewport]]
|
||||
[app.util.debug :as dbg]
|
||||
|
@ -206,7 +205,6 @@
|
|||
:style {:background-color background-color
|
||||
:touch-action "none"}}
|
||||
[:& context-menu]
|
||||
[:& token-context-menu]
|
||||
|
||||
(if ^boolean file-ready?
|
||||
[:& workspace-page {:page-id page-id
|
||||
|
|
|
@ -14,10 +14,8 @@
|
|||
[app.config :as cf]
|
||||
[app.main.data.events :as-alias ev]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.grid-layout.editor :as dwge]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.features :as features]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
|
@ -31,6 +29,7 @@
|
|||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.tokens.core :as wtc]
|
||||
[app.main.ui.workspace.tokens.editable-select :refer [editable-select]]
|
||||
[app.main.ui.workspace.tokens.token-types :as wtty]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
|
@ -864,7 +863,7 @@
|
|||
(wtc/tokens-name-map->select-options
|
||||
{:shape shape
|
||||
:tokens spacing-tokens
|
||||
:attributes (wtc/token-attributes :spacing)
|
||||
:attributes (wtty/token-attributes :spacing)
|
||||
:selected-attributes #{:spacing-column}})))
|
||||
|
||||
spacing-row-options (mf/use-memo
|
||||
|
@ -873,7 +872,7 @@
|
|||
(wtc/tokens-name-map->select-options
|
||||
{:shape shape
|
||||
:tokens spacing-tokens
|
||||
:attributes (wtc/token-attributes :spacing)
|
||||
:attributes (wtty/token-attributes :spacing)
|
||||
:selected-attributes #{:spacing-row}})))
|
||||
|
||||
padding-x-options (mf/use-memo
|
||||
|
@ -882,7 +881,7 @@
|
|||
(wtc/tokens-name-map->select-options
|
||||
{:shape shape
|
||||
:tokens spacing-tokens
|
||||
:attributes (wtc/token-attributes :spacing)
|
||||
:attributes (wtty/token-attributes :spacing)
|
||||
:selected-attributes #{:padding-p1 :padding-p3}})))
|
||||
|
||||
padding-y-options (mf/use-memo
|
||||
|
@ -891,7 +890,7 @@
|
|||
(wtc/tokens-name-map->select-options
|
||||
{:shape shape
|
||||
:tokens spacing-tokens
|
||||
:attributes (wtc/token-attributes :spacing)
|
||||
:attributes (wtty/token-attributes :spacing)
|
||||
:selected-attributes #{:padding-p2 :padding-p4}})))
|
||||
|
||||
on-add-layout
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
[app.main.ui.workspace.tokens.core :as wtc]
|
||||
[app.main.ui.workspace.tokens.editable-select :refer [editable-select]]
|
||||
[app.main.ui.workspace.tokens.style-dictionary :as sd]
|
||||
[app.main.ui.workspace.tokens.token-types :as wtty]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[clojure.set :refer [rename-keys union]]
|
||||
|
@ -109,21 +110,21 @@
|
|||
#(wtc/tokens-name-map->select-options
|
||||
{:shape shape
|
||||
:tokens border-radius-tokens
|
||||
:attributes (wtc/token-attributes :border-radius)}))
|
||||
:attributes (wtty/token-attributes :border-radius)}))
|
||||
sizing-tokens (:sizing tokens-by-type)
|
||||
width-options (mf/use-memo
|
||||
(mf/deps shape sizing-tokens)
|
||||
#(wtc/tokens-name-map->select-options
|
||||
{:shape shape
|
||||
:tokens sizing-tokens
|
||||
:attributes (wtc/token-attributes :sizing)
|
||||
:attributes (wtty/token-attributes :sizing)
|
||||
:selected-attributes #{:width}}))
|
||||
height-options (mf/use-memo
|
||||
(mf/deps shape sizing-tokens)
|
||||
#(wtc/tokens-name-map->select-options
|
||||
{:shape shape
|
||||
:tokens sizing-tokens
|
||||
:attributes (wtc/token-attributes :sizing)
|
||||
:attributes (wtty/token-attributes :sizing)
|
||||
:selected-attributes #{:height}}))
|
||||
|
||||
flex-child? (->> selection-parents (some ctl/flex-layout?))
|
||||
|
@ -330,7 +331,7 @@
|
|||
(let [token-value (wtc/maybe-resolve-token-value token)]
|
||||
(st/emit!
|
||||
(change-radius (fn [shape]
|
||||
(-> (dt/unapply-token-id shape (wtc/token-attributes :border-radius))
|
||||
(-> (dt/unapply-token-id shape (wtty/token-attributes :border-radius))
|
||||
(ctsr/set-radius-1 token-value))))))))
|
||||
|
||||
on-radius-1-change
|
||||
|
@ -342,7 +343,7 @@
|
|||
(change-radius (fn [shape]
|
||||
(-> (dt/maybe-apply-token-to-shape {:token (when token-value value)
|
||||
:shape shape
|
||||
:attributes (wtc/token-attributes :border-radius)})
|
||||
:attributes (wtty/token-attributes :border-radius)})
|
||||
(ctsr/set-radius-1 (or token-value value)))))))))
|
||||
|
||||
on-radius-multi-change
|
||||
|
|
|
@ -18,6 +18,25 @@ If possible add video here from PR as well
|
|||
|
||||
## Changes
|
||||
|
||||
### 2024-07-25 - UX Improvements for the context menu
|
||||
|
||||
[Link to PR](https://github.com/tokens-studio/tokens-studio-for-penpot/pull/224)
|
||||
|
||||
Changes context menu behavior according to [Specs](https://github.com/tokens-studio/obsidian-docs/blob/31f0d7f98ff5ac922970f3009fe877cc02d6d0cd/Products/TS%20for%20Penpot/Specs/Token%20State%20Specs.md)
|
||||
|
||||
- Removing a token wont update the shape
|
||||
- Mixed selection (shapes with applied, shapes without applied) will always unapply token
|
||||
- Multi selection of shapes without token will apply the token to all
|
||||
- Every shape change and token applying should be one undo step now
|
||||
- Prevent token applying when nothign is selected
|
||||
- `All` is a toggle instead of a checkbox if all tokens have been applied
|
||||
- For instance with border radius the context menu can `:r1 :r2 :r3 :r4` which will highlight `All`
|
||||
- If one attribute is missing it will check the single attributes
|
||||
- Clicking a single attribute after clicking `All` will remove the other attributes
|
||||
- Fixed some issues for switching between split and uniform border radius
|
||||
- Clicking a token wont apply all attributes anymore. We apply only a select collection of attributes, which makes most sense. For instance on `sizing` we only apply `width` and `height` instead of all (`max-width`, `max-height`, `min-heigt`, `min-width`)
|
||||
|
||||
|
||||
### 2024-07-05 - UX Improvements when applying tokens
|
||||
|
||||
[Link to PR](https://github.com/tokens-studio/tokens-studio-for-penpot/compare/token-studio-develop...ux-improvements?body=&expand=1)
|
||||
|
|
176
frontend/src/app/main/ui/workspace/tokens/changes.cljs
Normal file
176
frontend/src/app/main/ui/workspace/tokens/changes.cljs
Normal file
|
@ -0,0 +1,176 @@
|
|||
;; 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) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.changes
|
||||
(:require
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.main.data.tokens :as dt]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.transforms :as dwt]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.workspace.tokens.style-dictionary :as sd]
|
||||
[app.main.ui.workspace.tokens.token :as wtt]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.set :as set]
|
||||
[potok.v2.core :as ptk]
|
||||
[promesa.core :as p]))
|
||||
|
||||
;; Token Updates ---------------------------------------------------------------
|
||||
|
||||
(defn apply-token
|
||||
"Apply `attributes` that match `token` for `shape-ids`.
|
||||
|
||||
Optionally remove attributes from `attributes-to-remove`,
|
||||
this is useful for applying a single attribute from an attributes set
|
||||
while removing other applied tokens from this set."
|
||||
[{:keys [attributes attributes-to-remove token shape-ids on-update-shape] :as _props}]
|
||||
(ptk/reify ::apply-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens])))
|
||||
(rx/mapcat
|
||||
(fn [sd-tokens]
|
||||
(let [undo-id (js/Symbol)
|
||||
resolved-value (-> (get sd-tokens (:id token))
|
||||
(wtt/resolve-token-value))
|
||||
tokenized-attributes (wtt/attributes-map attributes (:id token))]
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dch/update-shapes shape-ids (fn [shape]
|
||||
(cond-> shape
|
||||
attributes-to-remove (update :applied-tokens #(apply (partial dissoc %) attributes-to-remove))
|
||||
:always (update :applied-tokens merge tokenized-attributes))))
|
||||
(when on-update-shape
|
||||
(on-update-shape resolved-value shape-ids attributes))
|
||||
(dwu/commit-undo-transaction undo-id)))))))))
|
||||
|
||||
(defn unapply-token
|
||||
"Removes `attributes` that match `token` for `shape-ids`.
|
||||
|
||||
Doesn't update shape attributes."
|
||||
[{:keys [attributes token shape-ids] :as _props}]
|
||||
(ptk/reify ::unapply-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of
|
||||
(let [remove-token #(when % (wtt/remove-attributes-for-token-id attributes (:id token) %))]
|
||||
(dch/update-shapes
|
||||
shape-ids
|
||||
(fn [shape]
|
||||
(update shape :applied-tokens remove-token))))))))
|
||||
|
||||
(defn toggle-token
|
||||
[{:keys [token-type-props token shapes] :as _props}]
|
||||
(ptk/reify ::on-toggle-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [attributes all-attributes on-update-shape]} token-type-props
|
||||
unapply-tokens? (wtt/shapes-token-applied? token shapes (or all-attributes attributes))
|
||||
shape-ids (map :id shapes)]
|
||||
(if unapply-tokens?
|
||||
(rx/of
|
||||
(unapply-token {:attributes (or all-attributes attributes)
|
||||
:token token
|
||||
:shape-ids shape-ids}))
|
||||
(rx/of
|
||||
(apply-token {:attributes attributes
|
||||
:token token
|
||||
:shape-ids shape-ids
|
||||
:on-update-shape on-update-shape})))))))
|
||||
|
||||
;; Shape Updates ---------------------------------------------------------------
|
||||
|
||||
(defn update-shape-radius-all [value shape-ids]
|
||||
(dch/update-shapes shape-ids
|
||||
(fn [shape]
|
||||
(when (ctsr/has-radius? shape)
|
||||
(ctsr/set-radius-1 shape value)))
|
||||
{:reg-objects? true
|
||||
:attrs ctt/border-radius-keys}))
|
||||
|
||||
(defn update-opacity [value shape-ids]
|
||||
(dch/update-shapes shape-ids #(assoc % :opacity value)))
|
||||
|
||||
(defn update-rotation [value shape-ids]
|
||||
(ptk/reify ::update-shape-dimensions
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of
|
||||
(udw/trigger-bounding-box-cloaking shape-ids)
|
||||
(udw/increase-rotation shape-ids value)))))
|
||||
|
||||
(defn update-shape-radius-single-corner [value shape-ids attributes]
|
||||
(dch/update-shapes shape-ids
|
||||
(fn [shape]
|
||||
(when (ctsr/has-radius? shape)
|
||||
(cond-> shape
|
||||
(:rx shape) (ctsr/switch-to-radius-4)
|
||||
:always (ctsr/set-radius-4 (first attributes) value))))
|
||||
{:reg-objects? true
|
||||
:attrs [:rx :ry :r1 :r2 :r3 :r4]}))
|
||||
|
||||
(defn update-stroke-width
|
||||
[value shape-ids]
|
||||
(dch/update-shapes shape-ids (fn [shape]
|
||||
(when (seq (:strokes shape))
|
||||
(assoc-in shape [:strokes 0 :stroke-width] value)))))
|
||||
|
||||
(defn update-shape-dimensions [value shape-ids attributes]
|
||||
(ptk/reify ::update-shape-dimensions
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of
|
||||
(when (:width attributes) (dwt/update-dimensions shape-ids :width value))
|
||||
(when (:height attributes) (dwt/update-dimensions shape-ids :height value))))))
|
||||
|
||||
(defn- attributes->layout-gap [attributes value]
|
||||
(let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap})
|
||||
(zipmap (repeat value)))]
|
||||
{:layout-gap layout-gap}))
|
||||
|
||||
(defn update-layout-spacing [value shape-ids attributes]
|
||||
(ptk/reify ::update-layout-spacing
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [layout-shape-ids (->> (wsh/lookup-shapes state shape-ids)
|
||||
(eduction
|
||||
(filter :layout)
|
||||
(map :id)))
|
||||
layout-attributes (attributes->layout-gap attributes value)]
|
||||
(rx/of
|
||||
(dwsl/update-layout layout-shape-ids layout-attributes))))))
|
||||
|
||||
(defn update-layout-spacing-column [value shape-ids]
|
||||
(ptk/reify ::update-layout-spacing-column
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/concat
|
||||
(for [shape-id shape-ids]
|
||||
(let [layout-update {:layout-gap {:column-gap value :row-gap value}}]
|
||||
(dwsl/update-layout [shape-id] layout-update)))))))
|
||||
|
||||
(defn update-shape-position [value shape-ids attributes]
|
||||
(ptk/reify ::update-shape-position
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/concat
|
||||
(map #(dwt/update-position % (zipmap attributes (repeat value))) shape-ids)))))
|
||||
|
||||
(defn update-layout-sizing-limits [value shape-ids attributes]
|
||||
(ptk/reify ::update-layout-sizing-limits
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [props (-> {:layout-item-min-w value
|
||||
:layout-item-min-h value
|
||||
:layout-item-max-w value
|
||||
:layout-item-max-h value}
|
||||
(select-keys attributes))]
|
||||
(dwsl/update-layout-child shape-ids props)))))
|
|
@ -8,27 +8,228 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.shortcuts :as scd]
|
||||
[app.main.data.tokens :as dt]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.transforms :as dwt]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.tokens.core :as wtc]
|
||||
[app.main.ui.workspace.tokens.changes :as wtch]
|
||||
[app.main.ui.workspace.tokens.token :as wtt]
|
||||
[app.main.ui.workspace.tokens.token-types :as wtty]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.timers :as timers]
|
||||
[clojure.set :as set]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
;; Actions ---------------------------------------------------------------------
|
||||
|
||||
(defn attribute-actions [token selected-shapes attributes]
|
||||
(let [ids-by-attributes (wtt/shapes-ids-by-applied-attributes token selected-shapes attributes)
|
||||
shape-ids (into #{} (map :id selected-shapes))]
|
||||
{:all-selected? (wtt/shapes-applied-all? ids-by-attributes shape-ids attributes)
|
||||
:shape-ids shape-ids
|
||||
:selected-pred #(seq (% ids-by-attributes))}))
|
||||
|
||||
(defn generic-attribute-actions [attributes title {:keys [token selected-shapes on-update-shape]}]
|
||||
(let [on-update-shape-fn (or on-update-shape
|
||||
(-> (wtty/get-token-properties token)
|
||||
(:on-update-shape)))
|
||||
{:keys [selected-pred shape-ids]} (attribute-actions token selected-shapes attributes)]
|
||||
(map (fn [attribute]
|
||||
(let [selected? (selected-pred attribute)
|
||||
props {:attributes #{attribute}
|
||||
:token token
|
||||
:shape-ids shape-ids}]
|
||||
|
||||
{:title title
|
||||
:selected? selected?
|
||||
:action (fn []
|
||||
(if selected?
|
||||
(st/emit! (wtch/unapply-token props))
|
||||
(st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape-fn)))))}))
|
||||
attributes)))
|
||||
|
||||
(defn all-or-sepearate-actions [{:keys [attribute-labels on-update-shape-all on-update-shape]}
|
||||
{:keys [token selected-shapes]}]
|
||||
(let [attributes (set (keys attribute-labels))
|
||||
{:keys [all-selected? selected-pred shape-ids]} (attribute-actions token selected-shapes attributes)
|
||||
all-action (let [props {:attributes attributes
|
||||
:token token
|
||||
:shape-ids shape-ids}]
|
||||
{:title "All"
|
||||
:selected? all-selected?
|
||||
:action #(if all-selected?
|
||||
(st/emit! (wtch/unapply-token props))
|
||||
(st/emit! (wtch/apply-token (assoc props :on-update-shape (or on-update-shape-all on-update-shape)))))})
|
||||
single-actions (map (fn [[attr title]]
|
||||
(let [selected? (selected-pred attr)]
|
||||
{:title title
|
||||
:selected? (and (not all-selected?) selected?)
|
||||
:action #(let [props {:attributes #{attr}
|
||||
:token token
|
||||
:shape-ids shape-ids}
|
||||
event (cond
|
||||
all-selected? (-> (assoc props :attributes-to-remove attributes)
|
||||
(wtch/apply-token))
|
||||
selected? (wtch/unapply-token props)
|
||||
:else (-> (assoc props :on-update-shape on-update-shape)
|
||||
(wtch/apply-token)))]
|
||||
(st/emit! event))}))
|
||||
attribute-labels)]
|
||||
(concat [all-action] single-actions)))
|
||||
|
||||
(defn spacing-attribute-actions [{:keys [token selected-shapes] :as context-data}]
|
||||
(let [on-update-shape (fn [resolved-value shape-ids attrs]
|
||||
(dwsl/update-layout shape-ids {:layout-padding (zipmap attrs (repeat resolved-value))}))
|
||||
padding-attrs {:p1 "Top"
|
||||
:p2 "Right"
|
||||
:p3 "Bottom"
|
||||
:p4 "Left"}
|
||||
all-padding-attrs (into #{} (keys padding-attrs))
|
||||
{:keys [all-selected? selected-pred shape-ids]} (attribute-actions token selected-shapes all-padding-attrs)
|
||||
horizontal-attributes #{:p1 :p3}
|
||||
horizontal-padding-selected? (and
|
||||
(not all-selected?)
|
||||
(every? selected-pred horizontal-attributes))
|
||||
vertical-attributes #{:p2 :p4}
|
||||
vertical-padding-selected? (and
|
||||
(not all-selected?)
|
||||
(every? selected-pred vertical-attributes))
|
||||
padding-items [{:title "All"
|
||||
:selected? all-selected?
|
||||
:action (fn []
|
||||
(let [props {:attributes all-padding-attrs
|
||||
:token token
|
||||
:shape-ids shape-ids}]
|
||||
(if all-selected?
|
||||
(st/emit! (wtch/unapply-token props))
|
||||
(st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape))))))}
|
||||
{:title "Horizontal"
|
||||
:selected? horizontal-padding-selected?
|
||||
:action (fn []
|
||||
(let [props {:token token
|
||||
:shape-ids shape-ids}
|
||||
event (cond
|
||||
all-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attributes))
|
||||
horizontal-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove horizontal-attributes))
|
||||
:else (wtch/apply-token (assoc props
|
||||
:attributes horizontal-attributes
|
||||
:on-update-shape on-update-shape)))]
|
||||
(st/emit! event)))}
|
||||
{:title "Vertical"
|
||||
:selected? vertical-padding-selected?
|
||||
:action (fn []
|
||||
(let [props {:token token
|
||||
:shape-ids shape-ids}
|
||||
event (cond
|
||||
all-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attributes))
|
||||
vertical-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attributes))
|
||||
:else (wtch/apply-token (assoc props
|
||||
:attributes vertical-attributes
|
||||
:on-update-shape on-update-shape)))]
|
||||
(st/emit! event)))}]
|
||||
single-padding-items (->> padding-attrs
|
||||
(map (fn [[attr title]]
|
||||
(let [same-axis-selected? (cond
|
||||
(get horizontal-attributes attr) horizontal-padding-selected?
|
||||
(get vertical-attributes attr) vertical-padding-selected?
|
||||
:else true)
|
||||
selected? (and
|
||||
(not all-selected?)
|
||||
(not same-axis-selected?)
|
||||
(selected-pred attr))]
|
||||
{:title title
|
||||
:selected? selected?
|
||||
:action #(let [props {:attributes #{attr}
|
||||
:token token
|
||||
:shape-ids shape-ids}
|
||||
event (cond
|
||||
all-selected? (-> (assoc props :attributes-to-remove all-padding-attrs)
|
||||
(wtch/apply-token))
|
||||
selected? (wtch/unapply-token props)
|
||||
:else (-> (assoc props :on-update-shape on-update-shape)
|
||||
(wtch/apply-token)))]
|
||||
(st/emit! event))}))))
|
||||
gap-items (all-or-sepearate-actions {:attribute-labels {:column-gap "Column Gap"
|
||||
:row-gap "Row Gap"}
|
||||
:on-update-shape wtch/update-layout-spacing}
|
||||
context-data)]
|
||||
(concat padding-items
|
||||
single-padding-items
|
||||
[:separator]
|
||||
gap-items)))
|
||||
|
||||
(defn sizing-attribute-actions [context-data]
|
||||
(concat
|
||||
(all-or-sepearate-actions {:attribute-labels {:width "Width"
|
||||
:height "Height"}
|
||||
:on-update-shape wtch/update-shape-dimensions}
|
||||
context-data)
|
||||
[:separator]
|
||||
(all-or-sepearate-actions {:attribute-labels {:layout-item-min-w "Min Width"
|
||||
:layout-item-min-h "Min Height"}
|
||||
:on-update-shape wtch/update-layout-sizing-limits}
|
||||
context-data)
|
||||
[:separator]
|
||||
(all-or-sepearate-actions {:attribute-labels {:layout-item-max-w "Max Width"
|
||||
:layout-item-max-h "Max Height"}
|
||||
:on-update-shape wtch/update-layout-sizing-limits}
|
||||
context-data)))
|
||||
|
||||
(def shape-attribute-actions-map
|
||||
(let [stroke-width (partial generic-attribute-actions #{:stroke-width} "Stroke Width")]
|
||||
{:border-radius (partial all-or-sepearate-actions {:attribute-labels {:r1 "Top Left"
|
||||
:r2 "Top Right"
|
||||
:r4 "Bottom Left"
|
||||
:r3 "Bottom Right"}
|
||||
:on-update-shape-all wtch/update-shape-radius-all
|
||||
:on-update-shape wtch/update-shape-radius-single-corner})
|
||||
:spacing spacing-attribute-actions
|
||||
:sizing sizing-attribute-actions
|
||||
:rotation (partial generic-attribute-actions #{:rotation} "Rotation")
|
||||
:opacity (partial generic-attribute-actions #{:opacity} "Opacity")
|
||||
:stroke-width stroke-width
|
||||
:dimensions (fn [context-data]
|
||||
(concat
|
||||
[{:title "Spacing" :submenu :spacing}
|
||||
{:title "Sizing" :submenu :sizing}
|
||||
:separator
|
||||
{:title "Border Radius" :submenu :border-radius}]
|
||||
(stroke-width context-data)
|
||||
[:separator]
|
||||
(generic-attribute-actions #{:x} "X" (assoc context-data :on-update-shape wtch/update-shape-position))
|
||||
(generic-attribute-actions #{:y} "Y" (assoc context-data :on-update-shape wtch/update-shape-position))))}))
|
||||
|
||||
(defn default-actions [{:keys [token]}]
|
||||
(let [{:keys [modal]} (wtty/get-token-properties token)]
|
||||
[{:title "Delete Token"
|
||||
:action #(st/emit! (dt/delete-token (:id token)))}
|
||||
{:title "Duplicate Token"
|
||||
:action #(st/emit! (dt/duplicate-token (:id token)))}
|
||||
{:title "Edit Token"
|
||||
:action (fn [event]
|
||||
(let [{:keys [key fields]} modal
|
||||
token (dt/get-token-data-from-token-id (:id token))]
|
||||
(st/emit! dt/hide-token-context-menu)
|
||||
(dom/stop-propagation event)
|
||||
(modal/show! key {:x (.-clientX ^js event)
|
||||
:y (.-clientY ^js event)
|
||||
:position :right
|
||||
:fields fields
|
||||
:token token})))}]))
|
||||
|
||||
(defn selection-actions [{:keys [type token] :as context-data}]
|
||||
(let [with-actions (get shape-attribute-actions-map (or type (:type token)))
|
||||
attribute-actions (with-actions context-data)]
|
||||
(concat
|
||||
attribute-actions
|
||||
[:separator]
|
||||
(default-actions context-data))))
|
||||
|
||||
;; Components ------------------------------------------------------------------
|
||||
|
||||
(def tokens-menu-ref
|
||||
(l/derived :token-context-menu refs/workspace-local))
|
||||
|
||||
|
@ -39,312 +240,92 @@
|
|||
|
||||
(mf/defc menu-entry
|
||||
{::mf/props :obj}
|
||||
[{:keys [title shortcut on-click on-pointer-enter on-pointer-leave
|
||||
on-unmount children selected? icon disabled value]}]
|
||||
[{:keys [title value on-click selected? children submenu-offset]}]
|
||||
(let [submenu-ref (mf/use-ref nil)
|
||||
hovering? (mf/use-ref false)
|
||||
on-pointer-enter
|
||||
(mf/use-callback
|
||||
(fn []
|
||||
(mf/set-ref-val! hovering? true)
|
||||
(let [submenu-node (mf/ref-val submenu-ref)]
|
||||
(when (some? submenu-node)
|
||||
(dom/set-css-property! submenu-node "display" "block")))
|
||||
(when on-pointer-enter (on-pointer-enter))))
|
||||
|
||||
(when-let [submenu-node (mf/ref-val submenu-ref)]
|
||||
(dom/set-css-property! submenu-node "display" "block"))))
|
||||
on-pointer-leave
|
||||
(mf/use-callback
|
||||
(fn []
|
||||
(mf/set-ref-val! hovering? false)
|
||||
(let [submenu-node (mf/ref-val submenu-ref)]
|
||||
(when (some? submenu-node)
|
||||
(timers/schedule
|
||||
50
|
||||
#(when-not (mf/ref-val hovering?)
|
||||
(dom/set-css-property! submenu-node "display" "none")))))
|
||||
(when on-pointer-leave (on-pointer-leave))))
|
||||
|
||||
(when-let [submenu-node (mf/ref-val submenu-ref)]
|
||||
(timers/schedule 50 #(when-not (mf/ref-val hovering?)
|
||||
(dom/set-css-property! submenu-node "display" "none"))))))
|
||||
set-dom-node
|
||||
(mf/use-callback
|
||||
(fn [dom]
|
||||
(let [submenu-node (mf/ref-val submenu-ref)]
|
||||
(when (and (some? dom) (some? submenu-node))
|
||||
(dom/set-css-property! submenu-node "top" (str (.-offsetTop dom) "px"))))))]
|
||||
[:li
|
||||
{:class (stl/css :context-menu-item)
|
||||
:ref set-dom-node
|
||||
:data-value value
|
||||
:on-click on-click
|
||||
:on-pointer-enter on-pointer-enter
|
||||
:on-pointer-leave on-pointer-leave}
|
||||
(when selected?
|
||||
[:span {:class (stl/css :icon-wrapper)}
|
||||
[:span {:class (stl/css :selected-icon)} i/tick]])
|
||||
[:span {:class (stl/css :title)} title]
|
||||
(when children
|
||||
[:*
|
||||
[:span {:class (stl/css :submenu-icon)} i/arrow]
|
||||
[:ul {:class (stl/css :token-context-submenu)
|
||||
:ref submenu-ref
|
||||
:style {:display "none"
|
||||
:top 0
|
||||
:left (str submenu-offset "px")}
|
||||
:on-context-menu prevent-default}
|
||||
children]])]))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps on-unmount)
|
||||
(constantly on-unmount))
|
||||
|
||||
(if icon
|
||||
[:li {:class (stl/css :icon-menu-item)
|
||||
:disabled disabled
|
||||
:data-value value
|
||||
:ref set-dom-node
|
||||
:on-click on-click
|
||||
:on-pointer-enter on-pointer-enter
|
||||
:on-pointer-leave on-pointer-leave}
|
||||
[:span
|
||||
{:class (stl/css :icon-wrapper)}
|
||||
(if selected? [:span {:class (stl/css :selected-icon)}
|
||||
i/tick]
|
||||
[:span {:class (stl/css :selected-icon)}])
|
||||
[:span {:class (stl/css :shape-icon)} icon]]
|
||||
[:span {:class (stl/css :title)} title]]
|
||||
[:li {:class (stl/css :context-menu-item)
|
||||
:disabled disabled
|
||||
:ref set-dom-node
|
||||
:data-value value
|
||||
:on-click on-click
|
||||
:on-pointer-enter on-pointer-enter
|
||||
:on-pointer-leave on-pointer-leave}
|
||||
[:span {:class (stl/css :title)} title]
|
||||
(when shortcut
|
||||
[:span {:class (stl/css :shortcut)}
|
||||
(for [[idx sc] (d/enumerate (scd/split-sc shortcut))]
|
||||
[:span {:key (dm/str shortcut "-" idx)
|
||||
:class (stl/css :shortcut-key)} sc])])
|
||||
|
||||
(when (> (count children) 1)
|
||||
[:span {:class (stl/css :submenu-icon)} i/arrow])
|
||||
|
||||
(when (> (count children) 1)
|
||||
[:ul {:class (stl/css :token-context-submenu)
|
||||
:ref submenu-ref
|
||||
:style {:display "none" :left 235}
|
||||
:on-context-menu prevent-default}
|
||||
children])])))
|
||||
|
||||
(mf/defc menu-separator
|
||||
[]
|
||||
[:li {:class (stl/css :separator)}])
|
||||
|
||||
(defn update-shape-radius-single-corner [value shape-ids attribute]
|
||||
(st/emit!
|
||||
(dch/update-shapes shape-ids
|
||||
(fn [shape]
|
||||
(when (ctsr/has-radius? shape)
|
||||
(ctsr/set-radius-4 shape (first attribute) value)))
|
||||
{:reg-objects? true
|
||||
:attrs [:rx :ry :r1 :r2 :r3 :r4]})))
|
||||
|
||||
(defn apply-border-radius-token [{:keys [token-id token-type-props selected-shapes]} attributes]
|
||||
(let [token (dt/get-token-data-from-token-id token-id)
|
||||
updated-token-type-props (if (set/superset? #{:r1 :r2 :r3 :r4} attributes)
|
||||
(assoc token-type-props
|
||||
:on-update-shape update-shape-radius-single-corner
|
||||
:attributes attributes)
|
||||
token-type-props)]
|
||||
(wtc/on-apply-token {:token token
|
||||
:token-type-props updated-token-type-props
|
||||
:selected-shapes selected-shapes})))
|
||||
|
||||
(defn update-layout-spacing [value selected-shapes attributes]
|
||||
(doseq [shape selected-shapes]
|
||||
(let [shape-id (:id shape)]
|
||||
(if-let [layout-gap (cond
|
||||
(:row-gap attributes) {:row-gap value}
|
||||
(:column-gap attributes) {:column-gap value})]
|
||||
(st/emit! (dwsl/update-layout [shape-id] {:layout-gap layout-gap}))
|
||||
(when (:layout shape)
|
||||
(st/emit! (dwsl/update-layout [shape-id] {:layout-padding (zipmap attributes (repeat value))})))))))
|
||||
|
||||
|
||||
(defn apply-spacing-token [{:keys [token-id token-type-props selected-shapes]} attributes]
|
||||
(let [token (dt/get-token-data-from-token-id token-id)
|
||||
attributes (set attributes)
|
||||
updated-token-type-props (assoc token-type-props
|
||||
:on-update-shape (fn [value shape-ids]
|
||||
(update-layout-spacing value selected-shapes attributes))
|
||||
:attributes attributes)]
|
||||
(wtc/on-apply-token {:token token
|
||||
:token-type-props updated-token-type-props
|
||||
:selected-shapes selected-shapes})))
|
||||
|
||||
(defn update-shape-position [value shape-ids attributes]
|
||||
(doseq [shape-id shape-ids]
|
||||
(st/emit! (dw/update-position shape-id {(first attributes) value}))))
|
||||
|
||||
(defn apply-dimensions-token [{:keys [token-id token-type-props selected-shapes]} attributes]
|
||||
(let [token (dt/get-token-data-from-token-id token-id)
|
||||
attributes (set attributes)
|
||||
updated-token-type-props (cond
|
||||
(set/superset? #{:x :y} attributes)
|
||||
(assoc token-type-props
|
||||
:on-update-shape update-shape-position
|
||||
:attributes attributes)
|
||||
|
||||
(set/superset? #{:stroke-width} attributes)
|
||||
(assoc token-type-props
|
||||
:on-update-shape wtc/update-stroke-width
|
||||
:attributes attributes)
|
||||
|
||||
:else token-type-props)]
|
||||
(wtc/on-apply-token {:token token
|
||||
:token-type-props updated-token-type-props
|
||||
:selected-shapes selected-shapes})))
|
||||
|
||||
(defn update-shape-dimensions [value shape-ids attributes]
|
||||
(st/emit! (dwt/update-dimensions shape-ids (first attributes) value)))
|
||||
|
||||
(defn update-layout-sizing-limits [value shape-ids attributes]
|
||||
(st/emit! (dwsl/update-layout-child shape-ids {(first attributes) value})))
|
||||
|
||||
(defn apply-sizing-token [{:keys [token-id token-type-props selected-shapes]} attributes]
|
||||
(let [token (dt/get-token-data-from-token-id token-id)
|
||||
updated-token-type-props (cond
|
||||
(set/superset? #{:width :height} attributes)
|
||||
(assoc token-type-props
|
||||
:on-update-shape update-shape-dimensions
|
||||
:attributes attributes)
|
||||
|
||||
(set/superset? #{:layout-item-min-w :layout-item-max-w
|
||||
:layout-item-min-h :layout-item-max-h} attributes)
|
||||
(assoc token-type-props
|
||||
:on-update-shape update-layout-sizing-limits
|
||||
:attributes attributes)
|
||||
|
||||
:else token-type-props)]
|
||||
(wtc/on-apply-token {:token token
|
||||
:token-type-props updated-token-type-props
|
||||
:selected-shapes selected-shapes})))
|
||||
|
||||
(defn apply-rotation-opacity-stroke-token [{:keys [token-id token-type-props selected-shapes]} attributes]
|
||||
(let [token (dt/get-token-data-from-token-id token-id)]
|
||||
(wtc/on-apply-token {:token token
|
||||
:token-type-props token-type-props
|
||||
:selected-shapes selected-shapes})))
|
||||
|
||||
(defn additional-actions [{:keys [token-id token-type selected-shapes] :as context-data}]
|
||||
(let [attributes->actions (fn [update-fn coll]
|
||||
(for [{:keys [attributes] :as item} coll]
|
||||
(let [selected? (wtt/shapes-token-applied? {:id token-id} selected-shapes attributes)]
|
||||
(assoc item
|
||||
:action #(update-fn context-data attributes)
|
||||
:selected? selected?))))]
|
||||
(case token-type
|
||||
:border-radius (attributes->actions
|
||||
apply-border-radius-token
|
||||
[{:title "All" :attributes #{:r1 :r2 :r3 :r4}}
|
||||
{:title "Top Left" :attributes #{:r1}}
|
||||
{:title "Top Right" :attributes #{:r2}}
|
||||
{:title "Bottom Right" :attributes #{:r3}}
|
||||
{:title "Bottom Left" :attributes #{:r4}}])
|
||||
:spacing (attributes->actions
|
||||
apply-spacing-token
|
||||
[{:title "All" :attributes #{:p1 :p2 :p3 :p4}}
|
||||
{:title "Column Gap" :attributes #{:column-gap}}
|
||||
{:title "Vertical padding" :attributes #{:p1 :p3}}
|
||||
{:title "Horizontal padding" :attributes #{:p2 :p4}}
|
||||
{:title "Row Gap" :attributes #{:row-gap}}
|
||||
{:title "Top" :attributes #{:p1}}
|
||||
{:title "Right" :attributes #{:p2}}
|
||||
{:title "Bottom" :attributes #{:p3}}
|
||||
{:title "Left" :attributes #{:p4}}])
|
||||
|
||||
:sizing (attributes->actions
|
||||
apply-sizing-token
|
||||
[{:title "All" :attributes #{:width :height :layout-item-min-w :layout-item-max-w :layout-item-min-h :layout-item-max-h}}
|
||||
{:title "Width" :attributes #{:width}}
|
||||
{:title "Height" :attributes #{:height}}
|
||||
{:title "Min width" :attributes #{:layout-item-min-w}}
|
||||
{:title "Max width" :attributes #{:layout-item-max-w}}
|
||||
{:title "Min height" :attributes #{:layout-item-min-h}}
|
||||
{:title "Max height" :attributes #{:layout-item-max-h}}])
|
||||
|
||||
:dimensions (attributes->actions
|
||||
apply-dimensions-token
|
||||
[{:title "Spacing" :submenu :spacing}
|
||||
{:title "Sizing" :submenu :sizing}
|
||||
{:title "Border Radius" :submenu :border-radius}
|
||||
{:title "Border Width" :attributes #{:stroke-width}}
|
||||
{:title "x" :attributes #{:x}}
|
||||
{:title "y" :attributes #{:y}}])
|
||||
;;TODO: Background blur {:title "Background blur" :attributes #{:width}}])
|
||||
|
||||
:opacity (attributes->actions
|
||||
apply-rotation-opacity-stroke-token
|
||||
[{:title "opacity" :attributes #{:opacity}}])
|
||||
|
||||
:rotation (attributes->actions
|
||||
apply-rotation-opacity-stroke-token
|
||||
[{:title "rotation" :attributes #{:rotation}}])
|
||||
|
||||
:stroke-width (attributes->actions
|
||||
apply-rotation-opacity-stroke-token
|
||||
[{:title "stroke width" :attributes #{:stroke-width}}])
|
||||
|
||||
[])))
|
||||
|
||||
(defn generate-menu-entries [{:keys [token-id token-type-props token-type selected-shapes] :as context-data}]
|
||||
(let [{:keys [modal]} token-type-props
|
||||
default-actions [{:title "Delete Token" :action #(st/emit! (dt/delete-token token-id))}
|
||||
{:title "Duplicate Token" :action #(st/emit! (dt/duplicate-token token-id))}
|
||||
{:title "Edit Token" :action (fn [event]
|
||||
(let [{:keys [key fields]} modal
|
||||
token (dt/get-token-data-from-token-id token-id)]
|
||||
(st/emit! dt/hide-token-context-menu)
|
||||
(dom/stop-propagation event)
|
||||
(modal/show! key {:x (.-clientX ^js event)
|
||||
:y (.-clientY ^js event)
|
||||
:position :right
|
||||
:fields fields
|
||||
:token token})))}]
|
||||
specific-actions (additional-actions context-data)
|
||||
all-actions (concat specific-actions default-actions)]
|
||||
all-actions))
|
||||
|
||||
(mf/defc token-pill-context-menu
|
||||
[context-data]
|
||||
(let [menu-entries (generate-menu-entries context-data)]
|
||||
(for [[index {:keys [title action selected? children submenu]}] (d/enumerate menu-entries)]
|
||||
[:& menu-entry (cond-> {:key index
|
||||
:title title}
|
||||
(not submenu) (assoc :on-click action
|
||||
;; TODO: Allow selected items wihtout an icon for the context menu
|
||||
:icon (mf/html [:div {:class (stl/css-case :empty-icon true
|
||||
:hidden-icon (not selected?))}])
|
||||
:selected? selected?))
|
||||
(when submenu
|
||||
(let [submenu-entries (additional-actions (assoc context-data :token-type submenu))]
|
||||
(for [[index {:keys [title action selected?]}] (d/enumerate submenu-entries)]
|
||||
[:& menu-entry {:key index
|
||||
:title title
|
||||
:on-click action
|
||||
:icon (mf/html [:div {:class (stl/css-case :empty-icon true
|
||||
:hidden-icon (not selected?))}])
|
||||
:selected? selected?}])))])))
|
||||
(mf/defc menu-tree
|
||||
[{:keys [selected-shapes] :as context-data}]
|
||||
(let [entries (if (seq selected-shapes)
|
||||
(selection-actions context-data)
|
||||
(default-actions context-data))]
|
||||
(for [[index {:keys [title action selected? submenu] :as entry}] (d/enumerate entries)]
|
||||
[:* {:key (str title " " index)}
|
||||
(cond
|
||||
(= :separator entry) [:li {:class (stl/css :separator)}]
|
||||
submenu [:& menu-entry {:title title
|
||||
:submenu-offset (:submenu-offset context-data)}
|
||||
[:& menu-tree (assoc context-data :type submenu)]]
|
||||
:else [:& menu-entry
|
||||
{:title title
|
||||
:on-click action
|
||||
:selected? selected?}])])))
|
||||
|
||||
(mf/defc token-context-menu
|
||||
[]
|
||||
(let [mdata (mf/deref tokens-menu-ref)
|
||||
top (- (get-in mdata [:position :y]) 20)
|
||||
left (get-in mdata [:position :x])
|
||||
dropdown-ref (mf/use-ref)
|
||||
(let [mdata (mf/deref tokens-menu-ref)
|
||||
top (+ (get-in mdata [:position :y]) 5)
|
||||
left (+ (get-in mdata [:position :x]) 5)
|
||||
width (mf/use-state 0)
|
||||
dropdown-ref (mf/use-ref)
|
||||
objects (mf/deref refs/workspace-page-objects)
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
selected-shapes (into [] (keep (d/getf objects)) selected)]
|
||||
|
||||
selected-shapes (into [] (keep (d/getf objects)) selected)
|
||||
token-id (:token-id mdata)
|
||||
token (get (mf/deref refs/workspace-tokens) token-id)]
|
||||
(mf/use-effect
|
||||
(mf/deps mdata)
|
||||
#(let [dropdown (mf/ref-val dropdown-ref)]
|
||||
(when dropdown
|
||||
(let [bounding-rect (dom/get-bounding-rect dropdown)
|
||||
window-size (dom/get-window-size)
|
||||
delta-x (max (- (+ (:right bounding-rect) 250) (:width window-size)) 0)
|
||||
delta-y (max (- (:bottom bounding-rect) (:height window-size)) 0)
|
||||
new-style (str "top: " (- top delta-y) "px; "
|
||||
"left: " (- left delta-x) "px;")]
|
||||
(when (or (> delta-x 0) (> delta-y 0))
|
||||
(.setAttribute ^js dropdown "style" new-style))))))
|
||||
(fn []
|
||||
(when-let [node (mf/ref-val dropdown-ref)]
|
||||
(reset! width (.-offsetWidth node)))))
|
||||
[:& dropdown {:show (boolean mdata)
|
||||
:on-close #(st/emit! dt/hide-token-context-menu)}
|
||||
[:div {:class (stl/css :token-context-menu)
|
||||
:ref dropdown-ref
|
||||
:style {:top top :left left}
|
||||
:on-context-menu prevent-default}
|
||||
(when (= :token (:type mdata))
|
||||
(when token
|
||||
[:ul {:class (stl/css :context-list)}
|
||||
[:& token-pill-context-menu {:token-id (:token-id mdata)
|
||||
:token-type-props (:token-type-props mdata)
|
||||
:token-type (:token-type mdata)
|
||||
:selected-shapes selected-shapes}]])]]))
|
||||
[:& menu-tree {:submenu-offset @width
|
||||
:token token
|
||||
:selected-shapes selected-shapes}]])]]))
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
.token-context-menu {
|
||||
position: absolute;
|
||||
top: $s-40;
|
||||
left: $s-736;
|
||||
z-index: $z-index-4;
|
||||
}
|
||||
|
||||
|
@ -38,13 +36,14 @@
|
|||
}
|
||||
|
||||
.separator {
|
||||
height: $s-12;
|
||||
@include bodySmallTypography;
|
||||
margin: $s-6;
|
||||
border-block-start: $s-1 solid var(--panel-border-color);
|
||||
}
|
||||
|
||||
.context-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: $s-28;
|
||||
width: 100%;
|
||||
padding: $s-6;
|
||||
|
@ -52,27 +51,34 @@
|
|||
cursor: pointer;
|
||||
|
||||
.title {
|
||||
flex-grow: 1;
|
||||
@include bodySmallTypography;
|
||||
color: var(--menu-foreground-color);
|
||||
margin-left: calc(($s-32 + $s-28) / 2);
|
||||
}
|
||||
.shortcut {
|
||||
@include flexCenter;
|
||||
gap: $s-2;
|
||||
color: var(--menu-shortcut-foreground-color);
|
||||
.shortcut-key {
|
||||
@include bodySmallTypography;
|
||||
@include flexCenter;
|
||||
height: $s-20;
|
||||
padding: $s-2 $s-6;
|
||||
border-radius: $br-6;
|
||||
background-color: var(--menu-shortcut-background-color);
|
||||
|
||||
.icon-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.icon-wrapper + .title {
|
||||
margin-left: $s-6;
|
||||
}
|
||||
|
||||
.selected-icon {
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--menu-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-icon svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--menu-foreground-color);
|
||||
.submenu-icon {
|
||||
margin-left: $s-2;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--menu-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -84,60 +90,14 @@
|
|||
color: var(--menu-shortcut-foreground-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: 1px solid var(--menu-border-color-focus);
|
||||
background-color: var(--menu-background-color-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-menu-item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
height: $s-28;
|
||||
padding: $s-6;
|
||||
border-radius: $br-8;
|
||||
&:hover {
|
||||
background-color: var(--menu-background-color-hover);
|
||||
}
|
||||
|
||||
span.title {
|
||||
margin-left: $s-6;
|
||||
}
|
||||
|
||||
.selected-icon {
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--menu-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.shape-icon {
|
||||
margin-left: $s-2;
|
||||
svg {
|
||||
@extend .button-icon-small;
|
||||
stroke: var(--menu-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin: 0;
|
||||
&[disabled] {
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-menu-item[disabled],
|
||||
.context-menu-item[disabled] {
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
// TODO: Allow selected items wihtout an icon for the context menu
|
||||
.empty-icon {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.hidden-icon {
|
||||
width: 11px;
|
||||
}
|
||||
|
|
|
@ -6,29 +6,16 @@
|
|||
|
||||
(ns app.main.ui.workspace.tokens.core
|
||||
(:require
|
||||
[app.common.data :as d :refer [ordered-map]]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.main.data.tokens :as dt]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.transforms :as dwt]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.common.data :as d]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.workspace.tokens.style-dictionary :as sd]
|
||||
[app.main.ui.workspace.tokens.token :as wtt]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]
|
||||
[promesa.core :as p]))
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; Helpers ---------------------------------------------------------------------
|
||||
|
||||
(defn resolve-token-value [{:keys [value resolved-value] :as token}]
|
||||
(defn resolve-token-value [{:keys [value resolved-value] :as _token}]
|
||||
(or
|
||||
resolved-value
|
||||
(d/parse-double value)))
|
||||
|
@ -48,128 +35,6 @@
|
|||
(cond-> (assoc item :label name)
|
||||
(wtt/token-applied? item shape (or selected-attributes attributes)) (assoc :selected? true))))))
|
||||
|
||||
;; Shape Update Functions ------------------------------------------------------
|
||||
|
||||
(defn update-shape-radius [value shape-ids]
|
||||
(dch/update-shapes shape-ids
|
||||
(fn [shape]
|
||||
(when (ctsr/has-radius? shape)
|
||||
(ctsr/set-radius-1 shape value)))
|
||||
{:reg-objects? true
|
||||
:attrs ctt/border-radius-keys}))
|
||||
|
||||
(defn update-shape-dimensions [value shape-ids]
|
||||
(ptk/reify ::update-shape-dimensions
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of
|
||||
(dwt/update-dimensions shape-ids :width value)
|
||||
(dwt/update-dimensions shape-ids :height value)))))
|
||||
|
||||
(defn update-opacity [value shape-ids]
|
||||
(dch/update-shapes shape-ids #(assoc % :opacity value)))
|
||||
|
||||
(defn update-stroke-width
|
||||
[value shape-ids]
|
||||
(dch/update-shapes shape-ids (fn [shape]
|
||||
(if (seq (:strokes shape))
|
||||
(assoc-in shape [:strokes 0 :stroke-width] value)
|
||||
shape))))
|
||||
|
||||
(defn update-rotation [value shape-ids]
|
||||
(ptk/reify ::update-shape-dimensions
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of
|
||||
(udw/trigger-bounding-box-cloaking shape-ids)
|
||||
(udw/increase-rotation shape-ids value)))))
|
||||
|
||||
(defn update-layout-spacing-column [value shape-ids]
|
||||
(ptk/reify ::update-layout-spacing-column
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(rx/concat
|
||||
(for [shape-id shape-ids]
|
||||
(let [shape (dt/get-shape-from-state shape-id state)
|
||||
layout-direction (:layout-flex-dir shape)
|
||||
layout-update (if (or (= layout-direction :row-reverse) (= layout-direction :row))
|
||||
{:layout-gap {:column-gap value}}
|
||||
{:layout-gap {:row-gap value}})]
|
||||
(dwsl/update-layout [shape-id] layout-update)))))))
|
||||
|
||||
;; Events ----------------------------------------------------------------------
|
||||
|
||||
(defn apply-token
|
||||
[{:keys [attributes shape-ids token on-update-shape] :as _props}]
|
||||
(ptk/reify ::apply-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens])))
|
||||
(rx/mapcat
|
||||
(fn [sd-tokens]
|
||||
(let [undo-id (js/Symbol)
|
||||
resolved-value (-> (get sd-tokens (:id token))
|
||||
(resolve-token-value))
|
||||
tokenized-attributes (wtt/attributes-map attributes (:id token))]
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dch/update-shapes shape-ids (fn [shape]
|
||||
(update shape :applied-tokens merge tokenized-attributes)))
|
||||
(when on-update-shape
|
||||
(on-update-shape resolved-value shape-ids attributes))
|
||||
(dwu/commit-undo-transaction undo-id)))))))))
|
||||
|
||||
(defn unapply-token
|
||||
"Removes `attributes` that match `token` for `shape-ids`.
|
||||
|
||||
Doesn't update shape attributes."
|
||||
[{:keys [attributes token shape-ids] :as _props}]
|
||||
(ptk/reify ::unapply-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of
|
||||
(let [remove-token #(when % (wtt/remove-attributes-for-token-id attributes (:id token) %))]
|
||||
(dch/update-shapes
|
||||
shape-ids
|
||||
(fn [shape]
|
||||
(update shape :applied-tokens remove-token))))))))
|
||||
|
||||
(defn toggle-token
|
||||
[{:keys [token-type-props token shapes] :as _props}]
|
||||
(ptk/reify ::on-toggle-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [attributes on-update-shape]} token-type-props
|
||||
unapply-tokens? (wtt/shapes-token-applied? token shapes (:attributes token-type-props))
|
||||
shape-ids (map :id shapes)]
|
||||
(if unapply-tokens?
|
||||
(rx/of
|
||||
(unapply-token {:attributes attributes
|
||||
:token token
|
||||
:shape-ids shape-ids}))
|
||||
(rx/of
|
||||
(apply-token {:attributes attributes
|
||||
:token token
|
||||
:shape-ids shape-ids
|
||||
:on-update-shape on-update-shape})))))))
|
||||
|
||||
(defn on-apply-token [{:keys [token token-type-props selected-shapes] :as _props}]
|
||||
(let [{:keys [attributes on-apply on-update-shape]
|
||||
:or {on-apply dt/update-token-from-attributes}} token-type-props
|
||||
shape-ids (->> selected-shapes
|
||||
(eduction
|
||||
(remove #(wtt/shapes-token-applied? token % attributes))
|
||||
(map :id)))]
|
||||
|
||||
(p/let [sd-tokens (sd/resolve-workspace-tokens+ {:debug? true})]
|
||||
(let [resolved-token (get sd-tokens (:id token))
|
||||
resolved-token-value (resolve-token-value resolved-token)]
|
||||
(doseq [shape selected-shapes]
|
||||
(st/emit! (on-apply {:token-id (:id token)
|
||||
:shape-id (:id shape)
|
||||
:attributes attributes}))
|
||||
(on-update-shape resolved-token-value shape-ids attributes))))))
|
||||
|
||||
;; JSON export functions -------------------------------------------------------
|
||||
|
||||
(defn encode-tokens
|
||||
|
@ -196,104 +61,3 @@
|
|||
(let [all-tokens (deref refs/workspace-tokens)
|
||||
transformed-tokens-json (transform-tokens-into-json-format all-tokens)]
|
||||
(export-tokens-file transformed-tokens-json)))
|
||||
|
||||
;; Token types -----------------------------------------------------------------
|
||||
|
||||
(def token-types
|
||||
(ordered-map
|
||||
[:border-radius
|
||||
{:title "Border Radius"
|
||||
:attributes ctt/border-radius-keys
|
||||
:on-update-shape update-shape-radius
|
||||
:modal {:key :tokens/border-radius
|
||||
:fields [{:label "Border Radius"
|
||||
:key :border-radius}]}}]
|
||||
[:stroke-width
|
||||
{:title "Stroke Width"
|
||||
:attributes ctt/stroke-width-keys
|
||||
:on-update-shape update-stroke-width
|
||||
:modal {:key :tokens/stroke-width
|
||||
:fields [{:label "Stroke Width"
|
||||
:key :stroke-width}]}}]
|
||||
|
||||
[:sizing
|
||||
{:title "Sizing"
|
||||
:attributes #{:width :height}
|
||||
:on-update-shape update-shape-dimensions
|
||||
:modal {:key :tokens/sizing
|
||||
:fields [{:label "Sizing"
|
||||
:key :sizing}]}}]
|
||||
[:dimensions
|
||||
{:title "Dimensions"
|
||||
:attributes #{:width :height}
|
||||
:on-update-shape update-shape-dimensions
|
||||
:modal {:key :tokens/dimensions
|
||||
:fields [{:label "Dimensions"
|
||||
:key :dimensions}]}}]
|
||||
|
||||
[:opacity
|
||||
{:title "Opacity"
|
||||
:attributes ctt/opacity-keys
|
||||
:on-update-shape update-opacity
|
||||
:modal {:key :tokens/opacity
|
||||
:fields [{:label "Opacity"
|
||||
:key :opacity}]}}]
|
||||
|
||||
[:rotation
|
||||
{:title "Rotation"
|
||||
:attributes ctt/rotation-keys
|
||||
:on-update-shape update-rotation
|
||||
:modal {:key :tokens/rotation
|
||||
:fields [{:label "Rotation"
|
||||
:key :rotation}]}}]
|
||||
[:spacing
|
||||
{:title "Spacing"
|
||||
:attributes ctt/spacing-keys
|
||||
:on-update-shape update-layout-spacing-column
|
||||
:modal {:key :tokens/spacing
|
||||
:fields [{:label "Spacing"
|
||||
:key :spacing}]}}]
|
||||
(comment
|
||||
[:boolean
|
||||
{:title "Boolean"
|
||||
:modal {:key :tokens/boolean
|
||||
:fields [{:label "Boolean"}]}}]
|
||||
|
||||
[:box-shadow
|
||||
{:title "Box Shadow"
|
||||
:modal {:key :tokens/box-shadow
|
||||
:fields [{:label "Box shadows"
|
||||
:key :box-shadow
|
||||
:type :box-shadow}]}}]
|
||||
|
||||
[:numeric
|
||||
{:title "Numeric"
|
||||
:modal {:key :tokens/numeric
|
||||
:fields [{:label "Numeric"
|
||||
:key :numeric}]}}]
|
||||
|
||||
[:other
|
||||
{:title "Other"
|
||||
:modal {:key :tokens/other
|
||||
:fields [{:label "Other"
|
||||
:key :other}]}}]
|
||||
[:string
|
||||
{:title "String"
|
||||
:modal {:key :tokens/string
|
||||
:fields [{:label "String"
|
||||
:key :string}]}}]
|
||||
[:typography
|
||||
{:title "Typography"
|
||||
:modal {:key :tokens/typography
|
||||
:fields [{:label "Font" :key :font-family}
|
||||
{:label "Weight" :key :weight}
|
||||
{:label "Font Size" :key :font-size}
|
||||
{:label "Line Height" :key :line-height}
|
||||
{:label "Letter Spacing" :key :letter-spacing}
|
||||
{:label "Paragraph Spacing" :key :paragraph-spacing}
|
||||
{:label "Paragraph Indent" :key :paragraph-indent}
|
||||
{:label "Text Decoration" :key :text-decoration}
|
||||
{:label "Text Case" :key :text-case}]}}])))
|
||||
|
||||
(defn token-attributes [token-type]
|
||||
(get-in token-types [token-type :attributes]))
|
||||
|
|
|
@ -14,9 +14,12 @@
|
|||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.sidebar.assets.common :as cmm]
|
||||
[app.main.ui.workspace.tokens.changes :as wtch]
|
||||
[app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]]
|
||||
[app.main.ui.workspace.tokens.core :as wtc]
|
||||
[app.main.ui.workspace.tokens.style-dictionary :as sd]
|
||||
[app.main.ui.workspace.tokens.token :as wtt]
|
||||
[app.main.ui.workspace.tokens.token-types :as wtty]
|
||||
[app.util.dom :as dom]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
|
@ -70,7 +73,7 @@
|
|||
[{:keys [type tokens selected-shapes token-type-props]}]
|
||||
(let [open? (mf/deref (-> (l/key type)
|
||||
(l/derived lens:token-type-open-status)))
|
||||
{:keys [modal attributes title]} token-type-props
|
||||
{:keys [modal attributes all-attributes title]} token-type-props
|
||||
|
||||
on-context-menu (mf/use-fn
|
||||
(fn [event token]
|
||||
|
@ -78,8 +81,6 @@
|
|||
(dom/stop-propagation event)
|
||||
(st/emit! (dt/show-token-context-menu {:type :token
|
||||
:position (dom/get-client-position event)
|
||||
:token-type-props token-type-props
|
||||
:token-type type
|
||||
:token-id (:id token)}))))
|
||||
|
||||
on-toggle-open-click (mf/use-fn
|
||||
|
@ -99,10 +100,11 @@
|
|||
(mf/deps selected-shapes token-type-props)
|
||||
(fn [event token]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit!
|
||||
(wtc/toggle-token {:token token
|
||||
:shapes selected-shapes
|
||||
:token-type-props token-type-props}))))
|
||||
(when (seq selected-shapes)
|
||||
(st/emit!
|
||||
(wtch/toggle-token {:token token
|
||||
:shapes selected-shapes
|
||||
:token-type-props token-type-props})))))
|
||||
tokens-count (count tokens)]
|
||||
[:div {:on-click on-toggle-open-click}
|
||||
[:& cmm/asset-section {:icon (mf/fnc icon-wrapper [_]
|
||||
|
@ -123,7 +125,7 @@
|
|||
[:& token-pill
|
||||
{:key (:id token)
|
||||
:token token
|
||||
:highlighted? (wtt/shapes-token-applied? token selected-shapes attributes)
|
||||
:highlighted? (wtt/shapes-token-applied? token selected-shapes (or all-attributes attributes))
|
||||
:on-click #(on-token-pill-click % token)
|
||||
:on-context-menu #(on-context-menu % token)}])]])]]))
|
||||
|
||||
|
@ -132,7 +134,7 @@
|
|||
Sort each group alphabetically (by their `:token-key`)."
|
||||
[tokens]
|
||||
(let [tokens-by-type (wtc/group-tokens-by-type tokens)
|
||||
{:keys [empty filled]} (->> wtc/token-types
|
||||
{:keys [empty filled]} (->> wtty/token-types
|
||||
(map (fn [[token-key token-type-props]]
|
||||
{:token-key token-key
|
||||
:token-type-props token-type-props
|
||||
|
@ -154,6 +156,7 @@
|
|||
token-groups (mf/with-memo [tokens]
|
||||
(sorted-token-groups tokens))]
|
||||
[:article
|
||||
[:& token-context-menu]
|
||||
[:div.assets-bar
|
||||
(for [{:keys [token-key token-type-props tokens]} (concat (:filled token-groups)
|
||||
(:empty token-groups))]
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
(ns app.main.ui.workspace.tokens.token
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(defn resolve-token-value [{:keys [value resolved-value] :as _token}]
|
||||
(or
|
||||
resolved-value
|
||||
(d/parse-double value)))
|
||||
|
||||
(defn attributes-map
|
||||
"Creats an attributes map using collection of `attributes` for `id`."
|
||||
[attributes id]
|
||||
|
@ -18,20 +25,64 @@
|
|||
applied-tokens)
|
||||
(into {}))))
|
||||
|
||||
(defn token-attribute-applied?
|
||||
"Test if `token` is applied to a `shape` on single `token-attribute`."
|
||||
[token shape token-attribute]
|
||||
(when-let [id (get-in shape [:applied-tokens token-attribute])]
|
||||
(= (:id token) id)))
|
||||
|
||||
(defn token-applied?
|
||||
"Test if `token` is applied to a `shape` with the given `token-attributes`."
|
||||
"Test if `token` is applied to a `shape` with at least one of the one of the given `token-attributes`."
|
||||
[token shape token-attributes]
|
||||
(let [{:keys [id]} token
|
||||
applied-tokens (get shape :applied-tokens {})]
|
||||
(some (fn [attr]
|
||||
(= (get applied-tokens attr) id))
|
||||
token-attributes)))
|
||||
(some #(token-attribute-applied? token shape %) token-attributes))
|
||||
|
||||
(defn token-applied-attributes
|
||||
"Return a set of which `token-attributes` are applied with `token`."
|
||||
[token shape token-attributes]
|
||||
(-> (filter #(token-attribute-applied? token shape %) token-attributes)
|
||||
(set)))
|
||||
|
||||
(defn shapes-token-applied?
|
||||
"Test if `token` is applied to to any of `shapes` with the given `token-attributes`."
|
||||
"Test if `token` is applied to to any of `shapes` with at least one of the one of the given `token-attributes`."
|
||||
[token shapes token-attributes]
|
||||
(some #(token-applied? token % token-attributes) shapes))
|
||||
|
||||
(defn shapes-token-applied-all?
|
||||
"Test if `token` is applied to to any of `shapes` with at least one of the one of the given `token-attributes`."
|
||||
[token shapes token-attributes]
|
||||
(some #(token-applied? token % token-attributes) shapes))
|
||||
|
||||
(defn shapes-ids-by-applied-attributes [token shapes token-attributes]
|
||||
(reduce (fn [acc shape]
|
||||
(let [applied-ids-by-attribute (->> (map #(when (token-attribute-applied? token shape %)
|
||||
[% #{(:id shape)}])
|
||||
token-attributes)
|
||||
(filter some?)
|
||||
(into {}))]
|
||||
(merge-with into acc applied-ids-by-attribute)))
|
||||
{} shapes))
|
||||
|
||||
(defn shapes-applied-all? [ids-by-attributes shape-ids attributes]
|
||||
(every? #(set/superset? (get ids-by-attributes %) shape-ids) attributes))
|
||||
|
||||
(defn group-shapes-by-all-applied
|
||||
[token shapes token-attributes]
|
||||
(reduce
|
||||
(fn [acc cur-shape]
|
||||
(let [applied-attrs (token-applied-attributes token cur-shape token-attributes)]
|
||||
(cond
|
||||
(empty? applied-attrs) (update acc :none (fnil conj []) cur-shape)
|
||||
(= applied-attrs token-attributes) (update acc :all (fnil conj []) cur-shape)
|
||||
:else (reduce (fn [acc' cur']
|
||||
(update-in acc' [:some cur'] (fnil conj []) cur-shape))
|
||||
acc applied-attrs))))
|
||||
{} shapes))
|
||||
|
||||
(defn group-shapes-by-all-applied-all? [grouped-shapes]
|
||||
(and (seq (:all grouped-shapes))
|
||||
(empty? (:other grouped-shapes))
|
||||
(empty? (:some grouped-shapes))))
|
||||
|
||||
(defn token-name->path
|
||||
"Splits token-name into a path vector split by `.` characters.
|
||||
|
||||
|
|
120
frontend/src/app/main/ui/workspace/tokens/token_types.cljs
Normal file
120
frontend/src/app/main/ui/workspace/tokens/token_types.cljs
Normal file
|
@ -0,0 +1,120 @@
|
|||
;; 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) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.workspace.tokens.token-types
|
||||
(:require
|
||||
[app.common.data :as d :refer [ordered-map]]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.main.ui.workspace.tokens.changes :as wtch]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(def token-types
|
||||
(ordered-map
|
||||
[:border-radius
|
||||
{:title "Border Radius"
|
||||
:attributes ctt/border-radius-keys
|
||||
:on-update-shape wtch/update-shape-radius-all
|
||||
:modal {:key :tokens/border-radius
|
||||
:fields [{:label "Border Radius"
|
||||
:key :border-radius}]}}]
|
||||
[:stroke-width
|
||||
{:title "Stroke Width"
|
||||
:attributes ctt/stroke-width-keys
|
||||
:on-update-shape wtch/update-stroke-width
|
||||
:modal {:key :tokens/stroke-width
|
||||
:fields [{:label "Stroke Width"
|
||||
:key :stroke-width}]}}]
|
||||
|
||||
[:sizing
|
||||
{:title "Sizing"
|
||||
:attributes #{:width :height}
|
||||
:all-attributes ctt/sizing-keys
|
||||
:on-update-shape wtch/update-shape-dimensions
|
||||
:modal {:key :tokens/sizing
|
||||
:fields [{:label "Sizing"
|
||||
:key :sizing}]}}]
|
||||
[:dimensions
|
||||
{:title "Dimensions"
|
||||
:attributes #{:width :height}
|
||||
:all-attributes (set/union
|
||||
ctt/spacing-keys
|
||||
ctt/sizing-keys
|
||||
ctt/border-radius-keys
|
||||
ctt/stroke-width-keys)
|
||||
:on-update-shape wtch/update-shape-dimensions
|
||||
:modal {:key :tokens/dimensions
|
||||
:fields [{:label "Dimensions"
|
||||
:key :dimensions}]}}]
|
||||
|
||||
[:opacity
|
||||
{:title "Opacity"
|
||||
:attributes ctt/opacity-keys
|
||||
:on-update-shape wtch/update-opacity
|
||||
:modal {:key :tokens/opacity
|
||||
:fields [{:label "Opacity"
|
||||
:key :opacity}]}}]
|
||||
|
||||
[:rotation
|
||||
{:title "Rotation"
|
||||
:attributes ctt/rotation-keys
|
||||
:on-update-shape wtch/update-rotation
|
||||
:modal {:key :tokens/rotation
|
||||
:fields [{:label "Rotation"
|
||||
:key :rotation}]}}]
|
||||
[:spacing
|
||||
{:title "Spacing"
|
||||
:attributes ctt/spacing-keys
|
||||
:on-update-shape wtch/update-layout-spacing
|
||||
:modal {:key :tokens/spacing
|
||||
:fields [{:label "Spacing"
|
||||
:key :spacing}]}}]
|
||||
(comment
|
||||
[:boolean
|
||||
{:title "Boolean"
|
||||
:modal {:key :tokens/boolean
|
||||
:fields [{:label "Boolean"}]}}]
|
||||
|
||||
[:box-shadow
|
||||
{:title "Box Shadow"
|
||||
:modal {:key :tokens/box-shadow
|
||||
:fields [{:label "Box shadows"
|
||||
:key :box-shadow
|
||||
:type :box-shadow}]}}]
|
||||
|
||||
[:numeric
|
||||
{:title "Numeric"
|
||||
:modal {:key :tokens/numeric
|
||||
:fields [{:label "Numeric"
|
||||
:key :numeric}]}}]
|
||||
|
||||
[:other
|
||||
{:title "Other"
|
||||
:modal {:key :tokens/other
|
||||
:fields [{:label "Other"
|
||||
:key :other}]}}]
|
||||
[:string
|
||||
{:title "String"
|
||||
:modal {:key :tokens/string
|
||||
:fields [{:label "String"
|
||||
:key :string}]}}]
|
||||
[:typography
|
||||
{:title "Typography"
|
||||
:modal {:key :tokens/typography
|
||||
:fields [{:label "Font" :key :font-family}
|
||||
{:label "Weight" :key :weight}
|
||||
{:label "Font Size" :key :font-size}
|
||||
{:label "Line Height" :key :line-height}
|
||||
{:label "Letter Spacing" :key :letter-spacing}
|
||||
{:label "Paragraph Spacing" :key :paragraph-spacing}
|
||||
{:label "Paragraph Indent" :key :paragraph-indent}
|
||||
{:label "Text Decoration" :key :text-decoration}
|
||||
{:label "Text Case" :key :text-case}]}}])))
|
||||
|
||||
(defn get-token-properties [token]
|
||||
(get token-types (:type token)))
|
||||
|
||||
(defn token-attributes [token-type]
|
||||
(get-in token-types [token-type :attributes]))
|
|
@ -3,6 +3,7 @@
|
|||
[app.common.test-helpers.compositions :as ctho]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.main.ui.workspace.tokens.changes :as wtch]
|
||||
[app.main.ui.workspace.tokens.core :as wtc]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.pages :as thp]
|
||||
|
@ -27,155 +28,189 @@
|
|||
:type :border-radius})))
|
||||
|
||||
(t/deftest test-apply-token
|
||||
(t/testing "applying a token twice with the same attributes will override")
|
||||
(t/async
|
||||
done
|
||||
(let [file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtc/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:rx :ry}
|
||||
:token (toht/get-token file :token-1)
|
||||
:on-update-shape wtc/update-shape-radius})
|
||||
(wtc/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:rx :ry}
|
||||
:token (toht/get-token file :token-2)
|
||||
:on-update-shape wtc/update-shape-radius})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-2' (toht/get-token file' :token-2)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:rx (:applied-tokens rect-1')) (:id token-2')))
|
||||
(t/is (= (:ry (:applied-tokens rect-1')) (:id token-2')))
|
||||
(t/is (= (:rx rect-1') 24))
|
||||
(t/is (= (:ry rect-1') 24))))))))
|
||||
(t/testing "applying a token twice with the same attributes will override the previous applied token"
|
||||
(t/async
|
||||
done
|
||||
(let [file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:rx :ry}
|
||||
:token (toht/get-token file :token-1)
|
||||
:on-update-shape wtch/update-shape-radius-all})
|
||||
(wtch/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:rx :ry}
|
||||
:token (toht/get-token file :token-2)
|
||||
:on-update-shape wtch/update-shape-radius-all})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-2' (toht/get-token file' :token-2)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:rx (:applied-tokens rect-1')) (:id token-2')))
|
||||
(t/is (= (:ry (:applied-tokens rect-1')) (:id token-2')))
|
||||
(t/is (= (:rx rect-1') 24))
|
||||
(t/is (= (:ry rect-1') 24)))))))))
|
||||
|
||||
(t/deftest test-apply-token-overwrite
|
||||
(t/testing "removes old token attributes and applies only single attribute"
|
||||
(t/async
|
||||
done
|
||||
(let [file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [;; Apply `:token-1` to all border radius attributes
|
||||
(wtch/apply-token {:attributes #{:rx :ry :r1 :r2 :r3 :r4}
|
||||
:token (toht/get-token file :token-1)
|
||||
:shape-ids [(:id rect-1)]
|
||||
:on-update-shape wtch/update-shape-radius-all})
|
||||
;; Apply single `:r1` attribute to same shape
|
||||
;; while removing other attributes from the border-radius set
|
||||
;; but keep `:r4` for testing purposes
|
||||
(wtch/apply-token {:attributes #{:r1}
|
||||
:attributes-to-remove #{:rx :ry :r1 :r2 :r3}
|
||||
:token (toht/get-token file :token-2)
|
||||
:shape-ids [(:id rect-1)]
|
||||
:on-update-shape wtch/update-shape-radius-all})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-1' (toht/get-token file' :token-1)
|
||||
token-2' (toht/get-token file' :token-2)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/testing "other border-radius attributes got removed"
|
||||
(t/is (nil? (:rx (:applied-tokens rect-1')))))
|
||||
(t/testing "r1 got applied with :token-2"
|
||||
(t/is (= (:r1 (:applied-tokens rect-1')) (:id token-2'))))
|
||||
(t/testing "while :r4 was kept"
|
||||
(t/is (= (:r4 (:applied-tokens rect-1')) (:id token-1')))))))))));)))))))))))
|
||||
|
||||
(t/deftest test-apply-border-radius
|
||||
(t/testing "applies radius token and updates the shapes radius")
|
||||
(t/async
|
||||
done
|
||||
(let [file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtc/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:rx :ry}
|
||||
:token (toht/get-token file :token-2)
|
||||
:on-update-shape wtc/update-shape-radius})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-2' (toht/get-token file' :token-2)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:rx (:applied-tokens rect-1')) (:id token-2')))
|
||||
(t/is (= (:ry (:applied-tokens rect-1')) (:id token-2')))
|
||||
(t/is (= (:rx rect-1') 24))
|
||||
(t/is (= (:ry rect-1') 24))))))))
|
||||
(t/testing "applies radius token and updates the shapes radius"
|
||||
(t/async
|
||||
done
|
||||
(let [file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:rx :ry}
|
||||
:token (toht/get-token file :token-2)
|
||||
:on-update-shape wtch/update-shape-radius-all})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-2' (toht/get-token file' :token-2)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:rx (:applied-tokens rect-1')) (:id token-2')))
|
||||
(t/is (= (:ry (:applied-tokens rect-1')) (:id token-2')))
|
||||
(t/is (= (:rx rect-1') 24))
|
||||
(t/is (= (:ry rect-1') 24)))))))))
|
||||
|
||||
(t/deftest test-apply-dimensions
|
||||
(t/testing "applies dimensions token and updates the shapes width and height")
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (setup-file)
|
||||
(toht/add-token :token-target {:value "100"
|
||||
:name "dimensions.sm"
|
||||
:type :dimensions}))
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtc/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:width :height}
|
||||
:token (toht/get-token file :token-target)
|
||||
:on-update-shape wtc/update-shape-dimensions})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-target' (toht/get-token file' :token-target)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:width (:applied-tokens rect-1')) (:id token-target')))
|
||||
(t/is (= (:height (:applied-tokens rect-1')) (:id token-target')))
|
||||
(t/is (= (:width rect-1') 100))
|
||||
(t/is (= (:height rect-1') 100))))))))
|
||||
(t/testing "applies dimensions token and updates the shapes width and height"
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (setup-file)
|
||||
(toht/add-token :token-target {:value "100"
|
||||
:name "dimensions.sm"
|
||||
:type :dimensions}))
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:width :height}
|
||||
:token (toht/get-token file :token-target)
|
||||
:on-update-shape wtch/update-shape-dimensions})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-target' (toht/get-token file' :token-target)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:width (:applied-tokens rect-1')) (:id token-target')))
|
||||
(t/is (= (:height (:applied-tokens rect-1')) (:id token-target')))
|
||||
(t/is (= (:width rect-1') 100))
|
||||
(t/is (= (:height rect-1') 100)))))))))
|
||||
|
||||
(t/deftest test-apply-sizing
|
||||
(t/testing "applies sizing token and updates the shapes width and height")
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (setup-file)
|
||||
(toht/add-token :token-target {:value "100"
|
||||
:name "sizing.sm"
|
||||
:type :sizing}))
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtc/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:width :height}
|
||||
:token (toht/get-token file :token-target)
|
||||
:on-update-shape wtc/update-shape-dimensions})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-target' (toht/get-token file' :token-target)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:width (:applied-tokens rect-1')) (:id token-target')))
|
||||
(t/is (= (:height (:applied-tokens rect-1')) (:id token-target')))
|
||||
(t/is (= (:width rect-1') 100))
|
||||
(t/is (= (:height rect-1') 100))))))))
|
||||
(t/testing "applies sizing token and updates the shapes width and height"
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (setup-file)
|
||||
(toht/add-token :token-target {:value "100"
|
||||
:name "sizing.sm"
|
||||
:type :sizing}))
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:width :height}
|
||||
:token (toht/get-token file :token-target)
|
||||
:on-update-shape wtch/update-shape-dimensions})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-target' (toht/get-token file' :token-target)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:width (:applied-tokens rect-1')) (:id token-target')))
|
||||
(t/is (= (:height (:applied-tokens rect-1')) (:id token-target')))
|
||||
(t/is (= (:width rect-1') 100))
|
||||
(t/is (= (:height rect-1') 100)))))))))
|
||||
|
||||
(t/deftest test-apply-opacity
|
||||
(t/testing "applies opacity token and updates the shapes opacity")
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (setup-file)
|
||||
(toht/add-token :token-target {:value "0.5"
|
||||
:name "opacity.medium"
|
||||
:type :opacity}))
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtc/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:opacity}
|
||||
:token (toht/get-token file :token-target)
|
||||
:on-update-shape wtc/update-opacity})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-target' (toht/get-token file' :token-target)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:opacity (:applied-tokens rect-1')) (:id token-target')))
|
||||
;; TODO Fix opacity shape update not working?
|
||||
#_(t/is (= (:opacity rect-1') 0.5))))))))
|
||||
(t/testing "applies opacity token and updates the shapes opacity"
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (setup-file)
|
||||
(toht/add-token :token-target {:value "0.5"
|
||||
:name "opacity.medium"
|
||||
:type :opacity}))
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:opacity}
|
||||
:token (toht/get-token file :token-target)
|
||||
:on-update-shape wtch/update-opacity})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-target' (toht/get-token file' :token-target)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:opacity (:applied-tokens rect-1')) (:id token-target')))
|
||||
;; TODO Fix opacity shape update not working?
|
||||
#_(t/is (= (:opacity rect-1') 0.5)))))))))
|
||||
|
||||
(t/deftest test-apply-rotation
|
||||
(t/testing "applies rotation token and updates the shapes rotation")
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (setup-file)
|
||||
(toht/add-token :token-target {:value "120"
|
||||
:name "rotation.medium"
|
||||
:type :rotation}))
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtc/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:rotation}
|
||||
:token (toht/get-token file :token-target)
|
||||
:on-update-shape wtc/update-rotation})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-target' (toht/get-token file' :token-target)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:rotation (:applied-tokens rect-1')) (:id token-target')))
|
||||
(t/is (= (:rotation rect-1') 120))))))))
|
||||
(t/testing "applies rotation token and updates the shapes rotation"
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (setup-file)
|
||||
(toht/add-token :token-target {:value "120"
|
||||
:name "rotation.medium"
|
||||
:type :rotation}))
|
||||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
|
||||
:attributes #{:rotation}
|
||||
:token (toht/get-token file :token-target)
|
||||
:on-update-shape wtch/update-rotation})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-store new-state)
|
||||
token-target' (toht/get-token file' :token-target)
|
||||
rect-1' (cths/get-shape file' :rect-1)]
|
||||
(t/is (some? (:applied-tokens rect-1')))
|
||||
(t/is (= (:rotation (:applied-tokens rect-1')) (:id token-target')))
|
||||
(t/is (= (:rotation rect-1') 120)))))))))
|
||||
|
||||
(t/deftest test-toggle-token-none
|
||||
(t/testing "should apply token to all selected items, where no item has the token applied"
|
||||
|
@ -185,10 +220,10 @@
|
|||
store (ths/setup-store file)
|
||||
rect-1 (cths/get-shape file :rect-1)
|
||||
rect-2 (cths/get-shape file :rect-2)
|
||||
events [(wtc/toggle-token {:shapes [rect-1 rect-2]
|
||||
:token-type-props {:attributes #{:rx :ry}
|
||||
:on-update-shape wtc/update-shape-radius}
|
||||
:token (toht/get-token file :token-2)})]]
|
||||
events [(wtch/toggle-token {:shapes [rect-1 rect-2]
|
||||
:token-type-props {:attributes #{:rx :ry}
|
||||
:on-update-shape wtch/update-shape-radius-all}
|
||||
:token (toht/get-token file :token-2)})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
|
@ -218,9 +253,9 @@
|
|||
rect-without-token (cths/get-shape file :rect-2)
|
||||
rect-with-other-token (cths/get-shape file :rect-3)
|
||||
|
||||
events [(wtc/toggle-token {:shapes [rect-with-token rect-without-token rect-with-other-token]
|
||||
:token (toht/get-token file :token-1)
|
||||
:token-type-props {:attributes #{:rx :ry}}})]]
|
||||
events [(wtch/toggle-token {:shapes [rect-with-token rect-without-token rect-with-other-token]
|
||||
:token (toht/get-token file :token-1)
|
||||
:token-type-props {:attributes #{:rx :ry}}})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
|
@ -252,9 +287,9 @@
|
|||
rect-without-token (cths/get-shape file :rect-2)
|
||||
rect-with-other-token-2 (cths/get-shape file :rect-3)
|
||||
|
||||
events [(wtc/toggle-token {:shapes [rect-with-other-token-1 rect-without-token rect-with-other-token-2]
|
||||
:token (toht/get-token file :token-1)
|
||||
:token-type-props {:attributes #{:rx :ry}}})]]
|
||||
events [(wtch/toggle-token {:shapes [rect-with-other-token-1 rect-without-token rect-with-other-token-2]
|
||||
:token (toht/get-token file :token-1)
|
||||
:token-type-props {:attributes #{:rx :ry}}})]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
|
|
|
@ -25,6 +25,40 @@
|
|||
(t/testing "doesn't match passed `:token-attributes`"
|
||||
(t/is (nil? (wtt/token-applied? {:id :a} {:applied-tokens {:x :a}} #{:y})))))
|
||||
|
||||
(t/deftest token-applied-attributes
|
||||
(t/is (= #{:x} (wtt/token-applied-attributes {:id :a}
|
||||
{:applied-tokens {:x :a :y :b}}
|
||||
#{:x :missing}))))
|
||||
(t/deftest shapes-ids-by-applied-attributes
|
||||
(t/testing "Returns set of matched attributes that fit the applied token"
|
||||
(let [attributes #{:x :y :z}
|
||||
shape-applied-x {:id :shape-applied-x
|
||||
:applied-tokens {:x 1}}
|
||||
shape-applied-y {:id :shape-applied-y
|
||||
:applied-tokens {:y 1}}
|
||||
shape-applied-x-y {:id :shape-applied-x-y
|
||||
:applied-tokens {:x 1 :y 1}}
|
||||
shape-applied-none {:id :shape-applied-none
|
||||
:applied-tokens {}}
|
||||
shape-applied-all {:id :shape-applied-all
|
||||
:applied-tokens {:x 1 :y 1 :z 1}}
|
||||
ids-set (fn [& xs] (into #{} (map :id xs)))
|
||||
shapes [shape-applied-x
|
||||
shape-applied-y
|
||||
shape-applied-x-y
|
||||
shape-applied-all
|
||||
shape-applied-none]
|
||||
expected (wtt/shapes-ids-by-applied-attributes {:id 1} shapes attributes)]
|
||||
(t/is (= (:x expected) (ids-set shape-applied-x
|
||||
shape-applied-x-y
|
||||
shape-applied-all)))
|
||||
(t/is (= (:y expected) (ids-set shape-applied-y
|
||||
shape-applied-x-y
|
||||
shape-applied-all)))
|
||||
(t/is (= (:z expected) (ids-set shape-applied-all)))
|
||||
(t/is (true? (wtt/shapes-applied-all? expected (ids-set shape-applied-all) attributes)))
|
||||
(t/is (false? (wtt/shapes-applied-all? expected (apply ids-set shapes) attributes))))))
|
||||
|
||||
(t/deftest tokens-applied-test
|
||||
(t/testing "is true when single shape matches the token and attributes"
|
||||
(t/is (true? (wtt/shapes-token-applied? {:id :a} [{:applied-tokens {:x :a}}
|
||||
|
|
Loading…
Add table
Reference in a new issue