diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index e3e16b2d3..503941a8b 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -8,6 +8,7 @@ (:require [app.common.data.macros :as dm] [app.common.files.changes-builder :as pcb] + [app.common.geom.point :as gpt] [app.common.types.shape :as cts] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] @@ -74,6 +75,18 @@ (pcb/add-token token))] (rx/of (dch/commit-changes changes))))))) +(defn delete-token + [id] + (dm/assert! (uuid? id)) + (ptk/reify ::delete-token + ptk/WatchEvent + (watch [it state _] + (let [data (get state :workspace-data) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/delete-token id))] + (rx/of (dch/commit-changes changes)))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TEMP (Move to test) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -108,3 +121,19 @@ (= (toggle-or-apply-token shape-after-token-2-is-applied token-3) shape-after-token-3-is-applied) nil) + +;; Token Context Menu Functions ------------------------------------------------- + +(defn show-token-context-menu + [{:keys [position token-id] :as params}] + (dm/assert! (gpt/point? position)) + (ptk/reify ::show-token-context-menu + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :token-context-menu] params)))) + +(def hide-token-context-menu + (ptk/reify ::hide-token-context-menu + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :token-context-menu] nil)))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 969bb43a6..33e558446 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -196,6 +196,9 @@ (def context-menu (l/derived :context-menu workspace-local)) +(def token-context-menu + (l/derived :token-context-menu workspace-local)) + ;; page item that it is being edited (def editing-page-item (l/derived :page-item workspace-local)) diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 8b4ee10c1..420e722a2 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -29,6 +29,7 @@ [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] @@ -204,6 +205,7 @@ :style {:background-color background-color :touch-action "none"}} [:& context-menu] + [:& token-context-menu] (if ^boolean file-ready? [:& workspace-page {:page-id page-id diff --git a/frontend/src/app/main/ui/workspace/tokens/common.cljs b/frontend/src/app/main/ui/workspace/tokens/common.cljs index 0ecd7c505..39fc4f58b 100644 --- a/frontend/src/app/main/ui/workspace/tokens/common.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/common.cljs @@ -38,4 +38,4 @@ [:input {:ref input-ref :default-value default-value :autoFocus auto-focus? - :on-change on-change}]]) + :on-change on-change}]]) \ No newline at end of file diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs new file mode 100644 index 000000000..ec4fcb4c2 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -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/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.workspace.tokens.context-menu + (:require-macros [app.main.style :as stl]) + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.main.data.events :as ev] + [app.main.data.shortcuts :as scd] + [app.main.data.tokens :as dt] + [app.main.data.workspace :as dw] + [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.context-menu :refer [menu-entry prevent-default]] + [app.util.dom :as dom] + [app.util.timers :as timers] + [okulary.core :as l] + [rumext.v2 :as mf])) + +(def tokens-menu-ref + (l/derived :token-context-menu refs/workspace-local)) + +(mf/defc token-pill-context-menu + [{:keys [token-id]}] + (let [do-delete #(st/emit! (dt/delete-token token-id)) + do-duplicate #(js/console.log "Duplicating") + do-edit #(js/console.log "Editing")] + [:* + [:& menu-entry {:title "Delete Token" :on-click do-delete}] + [:& menu-entry {:title "Duplicate Token" :on-click do-duplicate}] + [:& menu-entry {:title "Edit Token" :on-click do-edit}]])) + +(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)] + + (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)))))) + + [:& 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)) + [:ul {:class (stl/css :context-list)} + [:& token-pill-context-menu {:token-id (:token-id mdata)}]])]])) \ No newline at end of file diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.scss b/frontend/src/app/main/ui/workspace/tokens/context_menu.scss new file mode 100644 index 000000000..49fe69662 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.scss @@ -0,0 +1,31 @@ +// 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 + +@import "refactor/common-refactor.scss"; + +.token-context-menu { + position: absolute; + top: $s-40; + left: $s-736; + z-index: $z-index-4; +} + +.context-list, +.token-context-submenu { + @include menuShadow; + display: grid; + width: $s-240; + padding: $s-4; + border-radius: $br-8; + border: $s-2 solid var(--panel-border-color); + background-color: var(--menu-background-color); + max-height: 100vh; + overflow-y: auto; +} + +.token-context-submenu { + position: absolute; +} diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 5ce4d085c..4a32b6370 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -19,16 +19,17 @@ (mf/defc token-pill {::mf/wrap-props false} - [{:keys [on-click token highlighted?]}] - (let [{:keys [name value]} token + [{:keys [on-click token highlighted? on-context-menu]}] + (let [{:keys [name value]} token] resolved-value (try (wtc/resolve-token-value token) (catch js/Error _ nil))] [:div {:class (stl/css-case :token-pill true - :token-pill-highlighted highlighted? + :token-pill-highlighted highlighted?) :token-pill-invalid (not resolved-value)) :title (str (if resolved-value "Token value: " "Invalid token value: ") value) - :on-click on-click} + :on-click on-click + :on-context-menu on-context-menu} name])) (mf/defc token-section-icon @@ -53,6 +54,15 @@ [{:keys [type file tokens selected-shapes token-type-props]}] (let [open? (mf/use-state false) {:keys [modal attributes title]} token-type-props + + on-context-menu (mf/use-fn + (fn [event token] + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dt/show-token-context-menu {:type :token + :position (dom/get-client-position event) + :token-id (:id token)})))) + on-toggle-open-click (mf/use-fn (mf/deps open? tokens) #(when (seq tokens) @@ -96,7 +106,8 @@ {:key (:id token) :token token :highlighted? (tokens-applied? token selected-shapes attributes) - :on-click #(on-token-pill-click % token)}])]])]])) + :on-click #(on-token-pill-click % token) + :on-context-menu #(on-context-menu % token)}])]])]])) (defn sorted-token-groups "Separate token-types into groups of `:empty` or `:filled` depending if tokens exist for that type.