Add improved abstraction for team permissions

Relevant changes:
- replace user-viewer? with can-edit removing many double
  negations on the code
- always use team permissions making the permissions access uniform
  around all the code
- expose team permissions to ui tree through ctx/team-permissions
  context
This commit is contained in:
Andrey Antukh 2024-10-18 16:23:24 +02:00 committed by Alonso Torres
parent b3fcbd91e4
commit d6da8afdce
29 changed files with 412 additions and 391 deletions

View file

@ -182,11 +182,10 @@
uchg (vec undo-changes) uchg (vec undo-changes)
rchg (vec redo-changes) rchg (vec redo-changes)
features (features/get-team-enabled-features state) features (features/get-team-enabled-features state)
user-viewer? (not (dm/get-in state [:workspace-file :permissions :can-edit]))] permissions (:permissions state)]
;; Prevent commit changes by a viewer team member (it really should never happen) ;; Prevent commit changes by a viewer team member (it really should never happen)
(if user-viewer? (when (:can-edit permissions)
(rx/empty)
(rx/of (-> params (rx/of (-> params
(assoc :undo-group undo-group) (assoc :undo-group undo-group)
(assoc :features features) (assoc :features features)

View file

@ -7,10 +7,11 @@
(ns app.main.data.common (ns app.main.data.common
"A general purpose events." "A general purpose events."
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.team :as tt] [app.common.types.team :as ctt]
[app.config :as cf] [app.config :as cf]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
@ -196,7 +197,7 @@
(rx/tap on-success) (rx/tap on-success)
(rx/catch on-error)))))) (rx/catch on-error))))))
(defn- change-role-msg (defn- get-change-role-msg
[role] [role]
(case role (case role
:viewer (tr "dashboard.permissions-change.viewer") :viewer (tr "dashboard.permissions-change.viewer")
@ -204,26 +205,23 @@
:admin (tr "dashboard.permissions-change.admin") :admin (tr "dashboard.permissions-change.admin")
:owner (tr "dashboard.permissions-change.owner"))) :owner (tr "dashboard.permissions-change.owner")))
(defn change-team-role
(defn change-team-permissions [{:keys [team-id role]}]
[{:keys [team-id role workspace?]}]
(dm/assert! (uuid? team-id)) (dm/assert! (uuid? team-id))
(dm/assert! (contains? tt/valid-roles role)) (dm/assert! (contains? ctt/valid-roles role))
(ptk/reify ::change-team-permissions
(ptk/reify ::change-team-role
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (ntf/info (change-role-msg role)))) (rx/of (ntf/info (get-change-role-msg role))))
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [route (if workspace? (let [permissions (get ctt/permissions-for-role role)]
[:workspace-file :permissions] (-> state
[:teams team-id :permissions])] (update :permissions merge permissions)
(update-in state route (update-in [:team :permissions] merge permissions)
(fn [permissions] (d/update-in-when [:teams team-id :permissions] merge permissions))))))
(merge permissions (get tt/permissions-for-role role))))))))
(defn team-membership-change (defn team-membership-change
[{:keys [team-id team-name change]}] [{:keys [team-id team-name change]}]
@ -232,11 +230,11 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(when (= :removed change) (when (= :removed change)
(let [msg (tr "dashboard.removed-from-team" team-name)] (let [message (tr "dashboard.removed-from-team" team-name)
profile (:profile state)]
(rx/concat (rx/concat
(rx/of (rt/nav :dashboard-projects {:team-id (get-in state [:profile :default-team-id])})) (rx/of (rt/nav :dashboard-projects {:team-id (:default-team-id profile)}))
(->> (rx/of (ntf/info msg)) (->> (rx/of (ntf/info message))
;; Delay so the navigation can finish ;; Delay so the navigation can finish
(rx/delay 250)))))))) (rx/delay 250))))))))

View file

@ -12,7 +12,7 @@
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.team :as tt] [app.common.types.team :as ctt]
[app.common.uri :as u] [app.common.uri :as u]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
@ -482,7 +482,7 @@
(defn update-team-member-role (defn update-team-member-role
[{:keys [role member-id] :as params}] [{:keys [role member-id] :as params}]
(dm/assert! (uuid? member-id)) (dm/assert! (uuid? member-id))
(dm/assert! (contains? tt/valid-roles role)) (dm/assert! (contains? ctt/valid-roles role))
(ptk/reify ::update-team-member-role (ptk/reify ::update-team-member-role
ptk/WatchEvent ptk/WatchEvent
@ -605,7 +605,7 @@
(sm/check-email! email)) (sm/check-email! email))
(dm/assert! (uuid? team-id)) (dm/assert! (uuid? team-id))
(dm/assert! (contains? tt/valid-roles role)) (dm/assert! (contains? ctt/valid-roles role))
(ptk/reify ::update-team-invitation-role (ptk/reify ::update-team-invitation-role
IDeref IDeref
@ -1211,19 +1211,18 @@
;; Notifications ;; Notifications
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- handle-change-team-role
(defn- handle-change-team-permissions-dashboard [params]
[msg] (ptk/reify ::handle-change-team-role
(ptk/reify ::handle-change-team-permissions-dashboard
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (dc/change-team-permissions (assoc msg :workspace? false)) (rx/of (dc/change-team-role params)
(modal/hide))))) (modal/hide)))))
(defn- process-message (defn- process-message
[{:keys [type] :as msg}] [{:keys [type] :as msg}]
(case type (case type
:notification (dc/handle-notification msg) :notification (dc/handle-notification msg)
:team-role-change (handle-change-team-permissions-dashboard msg) :team-role-change (handle-change-team-role msg)
:team-membership-change (dc/team-membership-change msg) :team-membership-change (dc/team-membership-change msg)
nil)) nil))

View file

@ -97,6 +97,7 @@
(update [_ state] (update [_ state]
(-> state (-> state
(assoc :team team) (assoc :team team)
(assoc :permissions (:permissions team))
(assoc :current-team-id (:id team)))) (assoc :current-team-id (:id team))))
ptk/WatchEvent ptk/WatchEvent

View file

@ -68,7 +68,7 @@
(ptk/reify ::set-workspace-read-only (ptk/reify ::set-workspace-read-only
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc-in state [:workspace-global :read-only?] read-only?)) (update state :workspace-global assoc :read-only? read-only?))
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]

View file

@ -97,26 +97,22 @@
(rx/concat stream (rx/of (dws/send endmsg))))))) (rx/concat stream (rx/of (dws/send endmsg)))))))
(defn- handle-change-team-role
(defn- handle-change-team-permissions
[{:keys [role] :as msg}] [{:keys [role] :as msg}]
(ptk/reify ::handle-change-team-permissions (ptk/reify ::handle-change-team-role
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(let [viewer? (= :viewer role)]
(rx/concat (rx/concat
(rx/of :interrupt (rx/of :interrupt
(dwe/clear-edition-mode) (dwe/clear-edition-mode)
(dwc/set-workspace-read-only false)) (dwc/set-workspace-read-only false))
(->> (rx/of (dc/change-team-permissions msg)) (->> (rx/of (dc/change-team-role msg))
;; Delay so anything that launched :interrupt can finish ;; Delay so anything that launched :interrupt can finish
(rx/delay 100)) (rx/delay 100))
(if viewer? (if (= :viewer role)
(rx/of (modal/hide) (rx/of (modal/hide)
(dwly/set-options-mode :inspect)) (dwly/set-options-mode :inspect))
(rx/of (dwly/set-options-mode :design)))))))) (rx/of (dwly/set-options-mode :design)))))))
(defn- process-message (defn- process-message
[{:keys [type] :as msg}] [{:keys [type] :as msg}]
@ -129,7 +125,7 @@
:file-change (handle-file-change msg) :file-change (handle-file-change msg)
:library-change (handle-library-change msg) :library-change (handle-library-change msg)
:notification (dc/handle-notification msg) :notification (dc/handle-notification msg)
:team-role-change (handle-change-team-permissions (assoc msg :workspace? true)) :team-role-change (handle-change-team-role msg)
:team-membership-change (dc/team-membership-change msg) :team-membership-change (dc/team-membership-change msg)
nil)) nil))

View file

@ -37,18 +37,16 @@
;; Shortcuts ;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn toggle-layout-flag (defn- toggle-layout-flag
[flag] [flag]
(-> (dw/toggle-layout-flag flag) (-> (dw/toggle-layout-flag flag)
(vary-meta assoc ::ev/origin "workspace-shortcuts"))) (vary-meta assoc ::ev/origin "workspace-shortcuts")))
(defn emit-when-no-readonly (defn- emit-when-no-readonly
[& events] [& events]
(let [file (deref refs/workspace-file) (let [can-edit? (:can-edit (deref refs/permissions))
user-viewer? (not (dm/get-in file [:permissions :can-edit])) read-only? (deref refs/workspace-read-only?)]
read-only? (or (deref refs/workspace-read-only?) (when (and can-edit? (not read-only?))
user-viewer?)]
(when-not read-only?
(run! st/emit! events)))) (run! st/emit! events))))
(def esc-pressed (def esc-pressed

View file

@ -7,7 +7,7 @@
(ns app.main.data.workspace.text.shortcuts (ns app.main.data.workspace.text.shortcuts
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.files.helpers :as cfh]
[app.common.text :as txt] [app.common.text :as txt]
[app.main.data.shortcuts :as ds] [app.main.data.shortcuts :as ds]
[app.main.data.workspace.texts :as dwt] [app.main.data.workspace.texts :as dwt]
@ -190,16 +190,19 @@
(defn- update-attrs-when-no-readonly [props] (defn- update-attrs-when-no-readonly [props]
(let [undo-id (js/Symbol) (let [undo-id (js/Symbol)
file (deref refs/workspace-file)
user-viewer? (not (dm/get-in file [:permissions :can-edit])) can-edit? (:can-edit (deref refs/permissions))
read-only? (or (deref refs/workspace-read-only?) read-only? (deref refs/workspace-read-only?)
user-viewer?)
shapes-with-children (deref refs/selected-shapes-with-children) text-shapes (->> (deref refs/selected-shapes-with-children)
text-shapes (filter #(= (:type %) :text) shapes-with-children) (filter cfh/text-shape?)
(not-empty))
props (if (> (count text-shapes) 1) props (if (> (count text-shapes) 1)
(blend-props text-shapes props) (blend-props text-shapes props)
props)] props)]
(when (and (not read-only?) text-shapes)
(when (and can-edit? (not read-only?) text-shapes)
(st/emit! (dwu/start-undo-transaction undo-id)) (st/emit! (dwu/start-undo-transaction undo-id))
(run! #(update-attrs % props) text-shapes) (run! #(update-attrs % props) text-shapes)
(st/emit! (dwu/commit-undo-transaction undo-id))))) (st/emit! (dwu/commit-undo-transaction undo-id)))))

View file

@ -27,6 +27,12 @@
(def profile (def profile
(l/derived :profile st/state)) (l/derived :profile st/state))
(def team
(l/derived :team st/state))
(def permissions
(l/derived :permissions st/state))
(def teams (def teams
(l/derived :teams st/state)) (l/derived :teams st/state))

View file

@ -32,4 +32,4 @@
(def is-component? (mf/create-context false)) (def is-component? (mf/create-context false))
(def sidebar (mf/create-context nil)) (def sidebar (mf/create-context nil))
(def user-viewer? (mf/create-context nil)) (def team-permissions (mf/create-context nil))

View file

@ -65,7 +65,8 @@
content-width (mf/use-state 0) content-width (mf/use-state 0)
project-id (:id project) project-id (:id project)
team-id (:id team) team-id (:id team)
you-viewer? (not (dm/get-in team [:permissions :can-edit]))
permissions (:permissions team)
dashboard-local (mf/deref refs/dashboard-local) dashboard-local (mf/deref refs/dashboard-local)
file-menu-open? (:menu-open dashboard-local) file-menu-open? (:menu-open dashboard-local)
@ -87,8 +88,9 @@
(mf/use-fn (mf/use-fn
#(st/emit! (dd/clear-selected-files))) #(st/emit! (dd/clear-selected-files)))
show-templates (and (contains? cf/flags :dashboard-templates-section) show-templates
(not you-viewer?))] (and (contains? cf/flags :dashboard-templates-section)
(not (:can-edit permissions)))]
(mf/with-effect [] (mf/with-effect []
(let [key1 (events/listen js/window "resize" on-resize)] (let [key1 (events/listen js/window "resize" on-resize)]
@ -117,7 +119,7 @@
:content-width @content-width}])] :content-width @content-width}])]
:dashboard-fonts :dashboard-fonts
[:& fonts-page {:team team :you-viewer? you-viewer?}] [:& fonts-page {:team team}]
:dashboard-font-providers :dashboard-font-providers
[:& font-providers-page {:team team}] [:& font-providers-page {:team team}]
@ -125,7 +127,7 @@
:dashboard-files :dashboard-files
(when project (when project
[:* [:*
[:& files-section {:team team :project project :you-viewer? you-viewer?}] [:& files-section {:team team :project project}]
(when show-templates (when show-templates
[:& templates-section {:profile profile [:& templates-section {:profile profile
:team-id team-id :team-id team-id
@ -138,7 +140,7 @@
:search-term search-term}] :search-term search-term}]
:dashboard-libraries :dashboard-libraries
[:& libraries-page {:team team :you-viewer? you-viewer?}] [:& libraries-page {:team team}]
:dashboard-team-members :dashboard-team-members
[:& team-members-page {:team team :profile profile :invite-email invite-email}] [:& team-members-page {:team team :profile profile :invite-email invite-email}]
@ -231,8 +233,7 @@
invite-email (-> route :query-params :invite-email) invite-email (-> route :query-params :invite-email)
teams (mf/deref refs/teams) team (mf/deref refs/team)
team (get teams team-id)
projects (mf/deref refs/dashboard-projects) projects (mf/deref refs/dashboard-projects)
project (get projects project-id) project (get projects project-id)
@ -261,6 +262,7 @@
[:& (mf/provider ctx/current-team-id) {:value team-id} [:& (mf/provider ctx/current-team-id) {:value team-id}
[:& (mf/provider ctx/current-project-id) {:value project-id} [:& (mf/provider ctx/current-project-id) {:value project-id}
[:& (mf/provider ctx/team-permissions) {:value (:permissions team)}
;; NOTE: dashboard events and other related functions assumes ;; NOTE: dashboard events and other related functions assumes
;; that the team is a implicit context variable that is ;; that the team is a implicit context variable that is
;; available using react context or accessing ;; available using react context or accessing
@ -286,4 +288,4 @@
:section section :section section
:search-term search-term :search-term search-term
:team team :team team
:invite-email invite-email}])])]])) :invite-email invite-email}])])]]]))

View file

@ -57,7 +57,7 @@
(mf/defc file-menu (mf/defc file-menu
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [files show? on-edit on-menu-close top left navigate? origin parent-id you-viewer?]}] [{:keys [files show? on-edit on-menu-close top left navigate? origin parent-id can-edit]}]
(assert (seq files) "missing `files` prop") (assert (seq files) "missing `files` prop")
(assert (boolean? show?) "missing `show?` prop") (assert (boolean? show?) "missing `show?` prop")
(assert (fn? on-edit) "missing `on-edit` prop") (assert (fn? on-edit) "missing `on-edit` prop")
@ -245,13 +245,12 @@
options options
(if multi? (if multi?
[(when-not you-viewer? [(when can-edit
{:name (tr "dashboard.duplicate-multi" file-count) {:name (tr "dashboard.duplicate-multi" file-count)
:id "duplicate-multi" :id "duplicate-multi"
:handler on-duplicate}) :handler on-duplicate})
(when (and (or (seq current-projects) (seq other-teams)) (when (and (or (seq current-projects) (seq other-teams)) can-edit)
(not you-viewer?))
{:name (tr "dashboard.move-to-multi" file-count) {:name (tr "dashboard.move-to-multi" file-count)
:id "file-move-multi" :id "file-move-multi"
:options sub-options}) :options sub-options})
@ -269,14 +268,12 @@
:id "file-standard-export-multi" :id "file-standard-export-multi"
:handler on-export-standard-files} :handler on-export-standard-files}
(when (and (:is-shared file) (when (and (:is-shared file) can-edit)
(not you-viewer?))
{:name (tr "labels.unpublish-multi-files" file-count) {:name (tr "labels.unpublish-multi-files" file-count)
:id "file-unpublish-multi" :id "file-unpublish-multi"
:handler on-del-shared}) :handler on-del-shared})
(when (and (not is-lib-page?) (when (and (not is-lib-page?) can-edit)
(not you-viewer?))
{:name :separator} {:name :separator}
{:name (tr "labels.delete-multi-files" file-count) {:name (tr "labels.delete-multi-files" file-count)
:id "file-delete-multi" :id "file-delete-multi"
@ -285,14 +282,12 @@
[{:name (tr "dashboard.open-in-new-tab") [{:name (tr "dashboard.open-in-new-tab")
:id "file-open-new-tab" :id "file-open-new-tab"
:handler on-new-tab} :handler on-new-tab}
(when (and (not is-search-page?) (when (and (not is-search-page?) can-edit)
(not you-viewer?))
{:name (tr "labels.rename") {:name (tr "labels.rename")
:id "file-rename" :id "file-rename"
:handler on-edit}) :handler on-edit})
(when (and (not is-search-page?) (when (and (not is-search-page?) can-edit)
(not you-viewer?))
{:name (tr "dashboard.duplicate") {:name (tr "dashboard.duplicate")
:id "file-duplicate" :id "file-duplicate"
:handler on-duplicate}) :handler on-duplicate})
@ -300,13 +295,13 @@
(when (and (not is-lib-page?) (when (and (not is-lib-page?)
(not is-search-page?) (not is-search-page?)
(or (seq current-projects) (seq other-teams)) (or (seq current-projects) (seq other-teams))
(not you-viewer?)) can-edit)
{:name (tr "dashboard.move-to") {:name (tr "dashboard.move-to")
:id "file-move-to" :id "file-move-to"
:options sub-options}) :options sub-options})
(when (and (not is-search-page?) (when (and (not is-search-page?)
(not you-viewer?)) can-edit)
(if (:is-shared file) (if (:is-shared file)
{:name (tr "dashboard.unpublish-shared") {:name (tr "dashboard.unpublish-shared")
:id "file-del-shared" :id "file-del-shared"
@ -330,10 +325,10 @@
:id "download-standard-file" :id "download-standard-file"
:handler on-export-standard-files} :handler on-export-standard-files}
(when (and (not is-lib-page?) (not is-search-page?) (not you-viewer?)) (when (and (not is-lib-page?) (not is-search-page?) can-edit)
{:name :separator}) {:name :separator})
(when (and (not is-lib-page?) (not is-search-page?) (not you-viewer?)) (when (and (not is-lib-page?) (not is-search-page?) can-edit)
{:name (tr "labels.delete") {:name (tr "labels.delete")
:id "file-delete" :id "file-delete"
:handler on-delete})])] :handler on-delete})])]

View file

@ -28,8 +28,10 @@
(def ^:private menu-icon (def ^:private menu-icon
(i/icon-xref :menu (stl/css :menu-icon))) (i/icon-xref :menu (stl/css :menu-icon)))
(mf/defc header (mf/defc header*
[{:keys [project create-fn you-viewer?] :as props}] {::mf/props :obj
::mf/private true}
[{:keys [project create-fn can-edit]}]
(let [local (mf/use-state (let [local (mf/use-state
{:menu-open false {:menu-open false
:edition false}) :edition false})
@ -72,8 +74,7 @@
[:div#dashboard-drafts-title {:class (stl/css :dashboard-title)} [:div#dashboard-drafts-title {:class (stl/css :dashboard-title)}
[:h1 (tr "labels.drafts")]] [:h1 (tr "labels.drafts")]]
(if (and (:edition @local) (if (and (:edition @local) can-edit)
(not you-viewer?))
[:& inline-edition [:& inline-edition
{:content (:name project) {:content (:name project)
:on-end (fn [name] :on-end (fn [name]
@ -89,7 +90,7 @@
(:name project)]])) (:name project)]]))
[:div {:class (stl/css :dashboard-header-actions)} [:div {:class (stl/css :dashboard-header-actions)}
(when-not you-viewer? (when ^boolean can-edit
[:a {:class (stl/css :btn-secondary :btn-small :new-file) [:a {:class (stl/css :btn-secondary :btn-small :new-file)
:tab-index "0" :tab-index "0"
:on-click on-create-click :on-click on-create-click
@ -106,7 +107,7 @@
:on-click toggle-pin :on-click toggle-pin
:on-key-down (fn [event] (when (kbd/enter? event) (toggle-pin event)))}]) :on-key-down (fn [event] (when (kbd/enter? event) (toggle-pin event)))}])
(when-not you-viewer? (when ^boolean can-edit
[:div {:class (stl/css :icon) [:div {:class (stl/css :icon)
:tab-index "0" :tab-index "0"
:on-click on-menu-click :on-click on-menu-click
@ -116,7 +117,7 @@
(on-menu-click event)))} (on-menu-click event)))}
menu-icon]) menu-icon])
(when-not you-viewer? (when ^boolean can-edit
[:& project-menu {:project project [:& project-menu {:project project
:show? (:menu-open @local) :show? (:menu-open @local)
:left (- (:x (:menu-pos @local)) 180) :left (- (:x (:menu-pos @local)) 180)
@ -126,8 +127,10 @@
:on-import on-import}])]])) :on-import on-import}])]]))
(mf/defc files-section (mf/defc files-section
[{:keys [project team you-viewer?] :as props}] {::mf/props :obj}
[{:keys [project team]}]
(let [files-map (mf/deref refs/dashboard-files) (let [files-map (mf/deref refs/dashboard-files)
can-edit? (-> team :permissions :can-edit)
project-id (:id project) project-id (:id project)
is-draft-proyect (:is-default project) is-draft-proyect (:is-default project)
@ -139,7 +142,7 @@
(sort-by :modified-at) (sort-by :modified-at)
(reverse))) (reverse)))
file-count (or (count files) 0) file-count (or (count files) 0)
empty-state-viewer (and you-viewer? empty-state-viewer (and (not can-edit?)
(= 0 file-count)) (= 0 file-count))
on-file-created on-file-created
@ -171,9 +174,9 @@
(dd/clear-selected-files))) (dd/clear-selected-files)))
[:* [:*
[:& header {:team team [:> header* {:team team
:can-edit can-edit?
:project project :project project
:you-viewer? you-viewer?
:create-fn create-file}] :create-fn create-file}]
[:section {:class (stl/css :dashboard-container :no-bg) [:section {:class (stl/css :dashboard-container :no-bg)
:ref rowref} :ref rowref}
@ -188,7 +191,7 @@
(tr "dashboard.empty-placeholder-files-subtitle"))}] (tr "dashboard.empty-placeholder-files-subtitle"))}]
[:& grid {:project project [:& grid {:project project
:files files :files files
:you-viewer? you-viewer? :can-edit can-edit?
:origin :files :origin :files
:create-fn create-file :create-fn create-file
:limit limit}])]])) :limit limit}])]]))

View file

@ -269,7 +269,7 @@
{::mf/props :obj {::mf/props :obj
::mf/private true ::mf/private true
::mf/memo true} ::mf/memo true}
[{:keys [font-id variants you-viewer?]}] [{:keys [font-id variants can-edit]}]
(let [font (first variants) (let [font (first variants)
menu-open* (mf/use-state false) menu-open* (mf/use-state false)
@ -361,11 +361,11 @@
[:div {:class (stl/css :table-field :variants)} [:div {:class (stl/css :table-field :variants)}
(for [{:keys [id] :as item} variants] (for [{:keys [id] :as item} variants]
[:div {:class (stl/css-case :variant true [:div {:class (stl/css-case :variant true
:inhert-variant you-viewer?) :inhert-variant (not can-edit))
:key (dm/str id)} :key (dm/str id)}
[:span {:class (stl/css :label)} [:span {:class (stl/css :label)}
[:& font-variant-display-name {:variant item}]] [:& font-variant-display-name {:variant item}]]
(when-not you-viewer? (when can-edit
[:span [:span
{:class (stl/css :icon :close) {:class (stl/css :icon :close)
:data-id (dm/str id) :data-id (dm/str id)
@ -384,7 +384,7 @@
:on-click on-cancel} :on-click on-cancel}
i/close]] i/close]]
(when-not you-viewer? (when can-edit
[:div {:class (stl/css :table-field :options)} [:div {:class (stl/css :table-field :options)}
[:span {:class (stl/css :icon) [:span {:class (stl/css :icon)
:on-click on-menu-open} :on-click on-menu-open}
@ -397,7 +397,7 @@
:on-edit on-edit}]]))])) :on-edit on-edit}]]))]))
(mf/defc installed-fonts (mf/defc installed-fonts
[{:keys [fonts you-viewer?] :as props}] [{:keys [fonts can-edit] :as props}]
(let [sterm (mf/use-state "") (let [sterm (mf/use-state "")
matches? matches?
@ -426,7 +426,7 @@
(group-by :font-id))] (group-by :font-id))]
[:& installed-font {:key (dm/str font-id "-installed") [:& installed-font {:key (dm/str font-id "-installed")
:font-id font-id :font-id font-id
:you-viewer? you-viewer? :can-edit can-edit
:variants variants}])] :variants variants}])]
(nil? fonts) (nil? fonts)
@ -435,27 +435,33 @@
[:div {:class (stl/css :label)} (tr "dashboard.loading-fonts")]] [:div {:class (stl/css :label)} (tr "dashboard.loading-fonts")]]
:else :else
(if you-viewer? (if ^boolean can-edit
[:> empty-placeholder* {:title (tr "dashboard.fonts.empty-placeholder-viewer")
:subtitle (tr "dashboard.fonts.empty-placeholder-viewer-sub")
:type 2}]
[:div {:class (stl/css :fonts-placeholder)} [:div {:class (stl/css :fonts-placeholder)}
[:div {:class (stl/css :icon)} i/text] [:div {:class (stl/css :icon)} i/text]
[:div {:class (stl/css :label)} (tr "dashboard.fonts.empty-placeholder")]]))])) [:div {:class (stl/css :label)} (tr "dashboard.fonts.empty-placeholder")]]
[:> empty-placeholder*
{:title (tr "dashboard.fonts.empty-placeholder-viewer")
:subtitle (tr "dashboard.fonts.empty-placeholder-viewer-sub")
:type 2}]))]))
(mf/defc fonts-page (mf/defc fonts-page
[{:keys [team you-viewer?] :as props}] {::mf/props :obj}
(let [fonts (mf/deref refs/dashboard-fonts)] [{:keys [team]}]
(let [fonts (mf/deref refs/dashboard-fonts)
permissions (:permissions team)
can-edit (:can-edit permissions)]
[:* [:*
[:& header {:team team :section :fonts}] [:& header {:team team :section :fonts}]
[:section {:class (stl/css :dashboard-container :dashboard-fonts)} [:section {:class (stl/css :dashboard-container :dashboard-fonts)}
(when-not you-viewer? (when ^boolean can-edit
[:& uploaded-fonts {:team team :installed-fonts fonts}]) [:& uploaded-fonts {:team team :installed-fonts fonts}])
[:& installed-fonts {:team team :fonts fonts :you-viewer? you-viewer?}]]])) [:& installed-fonts {:team team :fonts fonts :can-edit can-edit}]]]))
(mf/defc font-providers-page (mf/defc font-providers-page
[{:keys [team] :as props}] {::mf/props :obj}
[{:keys [team]}]
[:* [:*
[:& header {:team team :section :providers}] [:& header {:team team :section :providers}]
[:section {:class (stl/css :dashboard-container)} [:section {:class (stl/css :dashboard-container)}

View file

@ -73,7 +73,7 @@
(mf/defc grid-item-thumbnail (mf/defc grid-item-thumbnail
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [file-id revn thumbnail-id background-color you-viewer?]}] [{:keys [file-id revn thumbnail-id background-color can-edit]}]
(let [container (mf/use-ref) (let [container (mf/use-ref)
visible? (h/use-visible container :once? true)] visible? (h/use-visible container :once? true)]
@ -94,12 +94,12 @@
(when visible? (when visible?
(if thumbnail-id (if thumbnail-id
[:img {:class (stl/css :grid-item-thumbnail-image) [:img {:class (stl/css :grid-item-thumbnail-image)
:draggable (dm/str (not you-viewer?)) :draggable (dm/str can-edit)
:src (cf/resolve-media thumbnail-id) :src (cf/resolve-media thumbnail-id)
:loading "lazy" :loading "lazy"
:decoding "async"}] :decoding "async"}]
[:> loader* {:class (stl/css :grid-loader) [:> loader* {:class (stl/css :grid-loader)
:draggable (dm/str (not you-viewer?)) :draggable (dm/str can-edit)
:overlay true :overlay true
:title (tr "labels.loading")}]))])) :title (tr "labels.loading")}]))]))
@ -233,7 +233,7 @@
(mf/defc grid-item (mf/defc grid-item
{:wrap [mf/memo]} {:wrap [mf/memo]}
[{:keys [file origin library-view? you-viewer?] :as props}] [{:keys [file origin library-view? can-edit] :as props}]
(let [file-id (:id file) (let [file-id (:id file)
;; FIXME: this breaks react hooks rule, hooks should never to ;; FIXME: this breaks react hooks rule, hooks should never to
@ -276,10 +276,10 @@
on-drag-start on-drag-start
(mf/use-fn (mf/use-fn
(mf/deps selected-files you-viewer?) (mf/deps selected-files can-edit)
(fn [event] (fn [event]
(st/emit! (dd/hide-file-menu)) (st/emit! (dd/hide-file-menu))
(when-not you-viewer? (when can-edit
(let [offset (dom/get-offset-position (.-nativeEvent event)) (let [offset (dom/get-offset-position (.-nativeEvent event))
select-current? (not (contains? selected-files (:id file))) select-current? (not (contains? selected-files (:id file)))
@ -359,7 +359,7 @@
{:class (stl/css-case :selected selected? :library library-view?) {:class (stl/css-case :selected selected? :library library-view?)
:ref node-ref :ref node-ref
:title (:name file) :title (:name file)
:draggable (dm/str (not you-viewer?)) :draggable (dm/str can-edit)
:on-click on-select :on-click on-select
:on-key-down handle-key-down :on-key-down handle-key-down
:on-double-click on-navigate :on-double-click on-navigate
@ -372,7 +372,7 @@
[:& grid-item-library {:file file}] [:& grid-item-library {:file file}]
[:& grid-item-thumbnail [:& grid-item-thumbnail
{:file-id (:id file) {:file-id (:id file)
:you-viewer? you-viewer? :can-edit can-edit
:revn (:revn file) :revn (:revn file)
:thumbnail-id (:thumbnail-id file) :thumbnail-id (:thumbnail-id file)
:background-color (dm/get-in file [:data :options :background])}]) :background-color (dm/get-in file [:data :options :background])}])
@ -408,7 +408,7 @@
:show? (:menu-open dashboard-local) :show? (:menu-open dashboard-local)
:left (+ 24 (:x (:menu-pos dashboard-local))) :left (+ 24 (:x (:menu-pos dashboard-local)))
:top (:y (:menu-pos dashboard-local)) :top (:y (:menu-pos dashboard-local))
:you-viewer? you-viewer? :can-edit can-edit
:navigate? true :navigate? true
:on-edit on-edit :on-edit on-edit
:on-menu-close on-menu-close :on-menu-close on-menu-close
@ -416,7 +416,7 @@
:parent-id (str file-id "-action-menu")}]])]]]]])) :parent-id (str file-id "-action-menu")}]])]]]]]))
(mf/defc grid (mf/defc grid
[{:keys [files project origin limit library-view? create-fn you-viewer?] :as props}] [{:keys [files project origin limit library-view? create-fn can-edit] :as props}]
(let [dragging? (mf/use-state false) (let [dragging? (mf/use-state false)
project-id (:id project) project-id (:id project)
node-ref (mf/use-var nil) node-ref (mf/use-var nil)
@ -433,7 +433,7 @@
on-drag-enter on-drag-enter
(mf/use-fn (mf/use-fn
(fn [e] (fn [e]
(when-not you-viewer? (when can-edit
(when (and (not (dnd/has-type? e "penpot/files")) (when (and (not (dnd/has-type? e "penpot/files"))
(or (dnd/has-type? e "Files") (or (dnd/has-type? e "Files")
(dnd/has-type? e "application/x-moz-file"))) (dnd/has-type? e "application/x-moz-file")))
@ -464,7 +464,7 @@
(import-files (.-files (.-dataTransfer e))))))] (import-files (.-files (.-dataTransfer e))))))]
[:div {:class (stl/css :dashboard-grid) [:div {:class (stl/css :dashboard-grid)
:dragabble (dm/str (not you-viewer?)) :dragabble (dm/str can-edit)
:on-drag-enter on-drag-enter :on-drag-enter on-drag-enter
:on-drag-over on-drag-over :on-drag-over on-drag-over
:on-drag-leave on-drag-leave :on-drag-leave on-drag-leave
@ -486,18 +486,18 @@
:key (:id item) :key (:id item)
:navigate? true :navigate? true
:origin origin :origin origin
:you-viewer? you-viewer? :can-edit can-edit
:library-view? library-view?}])]) :library-view? library-view?}])])
:else :else
[:& empty-placeholder [:& empty-placeholder
{:limit limit {:limit limit
:you-viewer? you-viewer? :can-edit can-edit
:create-fn create-fn :create-fn create-fn
:origin origin}])])) :origin origin}])]))
(mf/defc line-grid-row (mf/defc line-grid-row
[{:keys [files selected-files dragging? limit you-viewer?] :as props}] [{:keys [files selected-files dragging? limit can-edit] :as props}]
(let [elements limit (let [elements limit
limit (if dragging? (dec limit) limit)] limit (if dragging? (dec limit) limit)]
[:ul {:class (stl/css :grid-row :no-wrap) [:ul {:class (stl/css :grid-row :no-wrap)
@ -511,12 +511,12 @@
{:id (:id item) {:id (:id item)
:file item :file item
:selected-files selected-files :selected-files selected-files
:you-viewer? you-viewer? :can-edit can-edit
:key (:id item) :key (:id item)
:navigate? false}])])) :navigate? false}])]))
(mf/defc line-grid (mf/defc line-grid
[{:keys [project team files limit create-fn you-viewer?] :as props}] [{:keys [project team files limit create-fn can-edit] :as props}]
(let [dragging? (mf/use-state false) (let [dragging? (mf/use-state false)
project-id (:id project) project-id (:id project)
team-id (:id team) team-id (:id team)
@ -535,9 +535,9 @@
on-drag-enter on-drag-enter
(mf/use-fn (mf/use-fn
(mf/deps selected-project you-viewer?) (mf/deps selected-project can-edit)
(fn [e] (fn [e]
(when-not you-viewer? (when can-edit
(cond (cond
(dnd/has-type? e "penpot/files") (dnd/has-type? e "penpot/files")
(do (do
@ -595,7 +595,7 @@
(import-files (.-files (.-dataTransfer e)))))))] (import-files (.-files (.-dataTransfer e)))))))]
[:div {:class (stl/css :dashboard-grid) [:div {:class (stl/css :dashboard-grid)
:dragabble (dm/str (not you-viewer?)) :dragabble (dm/str can-edit)
:on-drag-enter on-drag-enter :on-drag-enter on-drag-enter
:on-drag-over on-drag-over :on-drag-over on-drag-over
:on-drag-leave on-drag-leave :on-drag-leave on-drag-leave
@ -609,12 +609,12 @@
:team-id team-id :team-id team-id
:selected-files selected-files :selected-files selected-files
:dragging? @dragging? :dragging? @dragging?
:you-viewer? you-viewer? :can-edit can-edit
:limit limit}] :limit limit}]
:else :else
[:& empty-placeholder [:& empty-placeholder
{:dragging? @dragging? {:dragging? @dragging?
:limit limit :limit limit
:you-viewer? you-viewer? :can-edit can-edit
:create-fn create-fn}])])) :create-fn create-fn}])]))

View file

@ -19,9 +19,11 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc libraries-page (mf/defc libraries-page
[{:keys [team you-viewer?] :as props}] {::mf/props :obj}
[{:keys [team] :as props}]
(let [files-map (mf/deref refs/dashboard-shared-files) (let [files-map (mf/deref refs/dashboard-shared-files)
projects (mf/deref refs/dashboard-projects) projects (mf/deref refs/dashboard-projects)
can-edit (-> team :permissions :can-edit)
default-project (->> projects vals (d/seek :is-default)) default-project (->> projects vals (d/seek :is-default))
@ -56,6 +58,6 @@
:project default-project :project default-project
:origin :libraries :origin :libraries
:limit limit :limit limit
:you-viewer? you-viewer? :can-edit can-edit
:library-view? components-v2}]]])) :library-view? components-v2}]]]))

View file

@ -14,7 +14,7 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc empty-placeholder (mf/defc empty-placeholder
[{:keys [dragging? limit origin create-fn you-viewer?]}] [{:keys [dragging? limit origin create-fn can-edit]}]
(let [on-click (let [on-click
(mf/use-fn (mf/use-fn
(mf/deps create-fn) (mf/deps create-fn)
@ -28,11 +28,14 @@
[:li {:class (stl/css :grid-item :grid-empty-placeholder :dragged)}]] [:li {:class (stl/css :grid-item :grid-empty-placeholder :dragged)}]]
(= :libraries origin) (= :libraries origin)
[:> empty-placeholder* {:title (tr "dashboard.empty-placeholder-libraries-title") [:> empty-placeholder*
{:title (tr "dashboard.empty-placeholder-libraries-title")
:type 2 :type 2
:subtitle (when you-viewer? (tr "dashboard.empty-placeholder-libraries-subtitle-viewer-role")) :subtitle (when-not can-edit
(tr "dashboard.empty-placeholder-libraries-subtitle-viewer-role"))
:class (stl/css :empty-placeholder-libraries)} :class (stl/css :empty-placeholder-libraries)}
(when-not you-viewer?
(when can-edit
[:> i18n/tr-html* {:content (tr "dashboard.empty-placeholder-libraries") [:> i18n/tr-html* {:content (tr "dashboard.empty-placeholder-libraries")
:class (stl/css :placeholder-markdown) :class (stl/css :placeholder-markdown)
:tag-name "span"}])] :tag-name "span"}])]

View file

@ -7,7 +7,6 @@
(ns app.main.ui.dashboard.projects (ns app.main.ui.dashboard.projects
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.main.data.dashboard :as dd] [app.main.data.dashboard :as dd]
[app.main.data.events :as ev] [app.main.data.events :as ev]
@ -46,12 +45,12 @@
(mf/defc header (mf/defc header
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [you-viewer?]}] [{:keys [can-edit]}]
(let [on-click (mf/use-fn #(st/emit! (dd/create-project)))] (let [on-click (mf/use-fn #(st/emit! (dd/create-project)))]
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"} [:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
[:div#dashboard-projects-title {:class (stl/css :dashboard-title)} [:div#dashboard-projects-title {:class (stl/css :dashboard-title)}
[:h1 (tr "dashboard.projects-title")]] [:h1 (tr "dashboard.projects-title")]]
(when-not you-viewer? (when can-edit
[:button {:class (stl/css :btn-secondary :btn-small) [:button {:class (stl/css :btn-secondary :btn-small)
:on-click on-click :on-click on-click
:data-testid "new-project-button"} :data-testid "new-project-button"}
@ -101,13 +100,13 @@
(l/derived :builtin-templates st/state)) (l/derived :builtin-templates st/state))
(mf/defc project-item (mf/defc project-item
[{:keys [project first? team files you-viewer?] :as props}] [{:keys [project first? team files can-edit] :as props}]
(let [locale (mf/deref i18n/locale) (let [locale (mf/deref i18n/locale)
file-count (or (:count project) 0) file-count (or (:count project) 0)
project-id (:id project) project-id (:id project)
is-draft-proyect (:is-default project) is-draft-proyect (:is-default project)
team-id (:id team) team-id (:id team)
empty-state-viewer (and you-viewer? empty-state-viewer (and (not can-edit)
(= 0 file-count)) (= 0 file-count))
dstate (mf/deref refs/dashboard-local) dstate (mf/deref refs/dashboard-local)
@ -225,7 +224,7 @@
:title (if (:is-default project) :title (if (:is-default project)
(tr "labels.drafts") (tr "labels.drafts")
(:name project)) (:name project))
:on-context-menu (when-not you-viewer? on-menu-click)} :on-context-menu (when can-edit on-menu-click)}
(if (:is-default project) (if (:is-default project)
(tr "labels.drafts") (tr "labels.drafts")
(:name project))]) (:name project))])
@ -246,7 +245,7 @@
(when-not (:is-default project) (when-not (:is-default project)
[:> pin-button* {:class (stl/css :pin-button) :is-pinned (:is-pinned project) :on-click toggle-pin :tab-index 0}]) [:> pin-button* {:class (stl/css :pin-button) :is-pinned (:is-pinned project) :on-click toggle-pin :tab-index 0}])
(when-not you-viewer? (when ^boolean can-edit
[:button {:class (stl/css :add-file-btn) [:button {:class (stl/css :add-file-btn)
:on-click on-create-click :on-click on-create-click
:title (tr "dashboard.new-file") :title (tr "dashboard.new-file")
@ -255,7 +254,7 @@
:on-key-down handle-create-click} :on-key-down handle-create-click}
add-icon]) add-icon])
(when-not you-viewer? (when ^boolean can-edit
[:button {:class (stl/css :options-btn) [:button {:class (stl/css :options-btn)
:on-click on-menu-click :on-click on-menu-click
:title (tr "dashboard.options") :title (tr "dashboard.options")
@ -263,7 +262,8 @@
:data-testid "project-options" :data-testid "project-options"
:on-key-down handle-menu-click} :on-key-down handle-menu-click}
menu-icon])] menu-icon])]
(when-not you-viewer?
(when ^boolean can-edit
[:& project-menu [:& project-menu
{:project project {:project project
:show? (:menu-open @local) :show? (:menu-open @local)
@ -289,7 +289,7 @@
:team team :team team
:files files :files files
:create-fn create-file :create-fn create-file
:you-viewer? you-viewer? :can-edit can-edit
:limit limit}])] :limit limit}])]
(when (and (> limit 0) (when (and (> limit 0)
@ -309,14 +309,16 @@
(mf/defc projects-section (mf/defc projects-section
[{:keys [team projects profile] :as props}] [{:keys [team projects profile] :as props}]
(let [projects (->> (vals projects) (let [projects (->> (vals projects)
(sort-by :modified-at) (sort-by :modified-at)
(reverse)) (reverse))
recent-map (mf/deref recent-files-ref) recent-map (mf/deref recent-files-ref)
you-owner? (dm/get-in team [:permissions :is-owner]) permisions (:permissions team)
you-admin? (dm/get-in team [:permissions :is-admin])
you-viewer? (not (dm/get-in team [:permissions :can-edit])) can-edit (:can-edit permisions)
can-invite? (or you-owner? you-admin?) can-invite (or (:is-owner permisions)
(:is-admin permisions))
show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true)) show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true))
show-team-hero? (deref show-team-hero*) show-team-hero? (deref show-team-hero*)
@ -348,11 +350,11 @@
(when (seq projects) (when (seq projects)
[:* [:*
[:& header {:you-viewer? you-viewer?}] [:& header {:can-edit can-edit}]
[:div {:class (stl/css :projects-container)} [:div {:class (stl/css :projects-container)}
[:* [:*
(when (and show-team-hero? (when (and show-team-hero?
can-invite? can-invite
(not is-defalt-team?)) (not is-defalt-team?))
[:> team-hero* {:team team :on-close on-close}]) [:> team-hero* {:team team :on-close on-close}])
@ -362,7 +364,7 @@
:with-team-hero (and (not is-my-penpot) :with-team-hero (and (not is-my-penpot)
(not is-defalt-team?) (not is-defalt-team?)
show-team-hero? show-team-hero?
can-invite?))} can-invite))}
(for [{:keys [id] :as project} projects] (for [{:keys [id] :as project} projects]
(let [files (when recent-map (let [files (when recent-map
(->> (vals recent-map) (->> (vals recent-map)
@ -371,6 +373,6 @@
[:& project-item {:project project [:& project-item {:project project
:team team :team team
:files files :files files
:you-viewer? you-viewer? :can-edit can-edit
:first? (= project (first projects)) :first? (= project (first projects))
:key id}]))]]]]))) :key id}]))]]]])))

View file

@ -255,28 +255,30 @@
(mf/defc rol-info (mf/defc rol-info
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [member team on-set-admin on-set-editor on-set-owner on-set-viewer profile]}] [{:keys [member team on-set-admin on-set-editor on-set-owner on-set-viewer profile]}]
(let [member-is-owner? (:is-owner member) (let [member-is-owner (:is-owner member)
member-is-admin? (and (:is-admin member) (not member-is-owner?)) member-is-admin (and (:is-admin member) (not member-is-owner))
member-is-editor? (and (:can-edit member) (and (not member-is-admin?) (not member-is-owner?))) member-is-editor (and (:can-edit member) (and (not member-is-admin) (not member-is-owner)))
show? (mf/use-state false) show? (mf/use-state false)
you-owner? (dm/get-in team [:permissions :is-owner]) permissions (:permissions team)
you-admin? (dm/get-in team [:permissions :is-admin]) is-owner (:is-owner permissions)
is-you? (= (:id profile) (:id member)) is-admin (:is-admin permissions)
can-change-rol? (or you-owner? you-admin?) is-you (= (:id profile) (:id member))
not-superior? (or you-owner? (and can-change-rol? (or member-is-admin? member-is-editor?)))
can-change-rol (or is-owner is-admin)
not-superior (or is-admin (and can-change-rol (or member-is-admin member-is-editor)))
role (cond role (cond
member-is-owner? "labels.owner" member-is-owner "labels.owner"
member-is-admin? "labels.admin" member-is-admin "labels.admin"
member-is-editor? "labels.editor" member-is-editor "labels.editor"
:else "labels.viewer") :else "labels.viewer")
on-show (mf/use-fn #(reset! show? true)) on-show (mf/use-fn #(reset! show? true))
on-hide (mf/use-fn #(reset! show? false))] on-hide (mf/use-fn #(reset! show? false))]
[:* [:*
(if (and can-change-rol? not-superior? (not (and is-you? you-owner?))) (if (and can-change-rol not-superior (not (and is-you is-owner)))
[:div {:class (stl/css :rol-selector :has-priv) [:div {:class (stl/css :rol-selector :has-priv)
:on-click on-show} :on-click on-show}
[:span {:class (stl/css :rol-label)} (tr role)] [:span {:class (stl/css :rol-label)} (tr role)]
@ -295,7 +297,7 @@
[:li {:on-click on-set-viewer [:li {:on-click on-set-viewer
:class (stl/css :rol-dropdown-item)} :class (stl/css :rol-dropdown-item)}
(tr "labels.viewer")] (tr "labels.viewer")]
(when you-owner? (when is-owner
[:li {:on-click (partial on-set-owner member) [:li {:on-click (partial on-set-owner member)
:class (stl/css :rol-dropdown-item)} :class (stl/css :rol-dropdown-item)}
(tr "labels.owner")])]]])) (tr "labels.owner")])]]]))
@ -320,7 +322,6 @@
:on-click on-show} :on-click on-show}
menu-icon] menu-icon]
[:& dropdown {:show @show? :on-close on-hide} [:& dropdown {:show @show? :on-close on-hide}
[:ul {:class (stl/css :actions-dropdown)} [:ul {:class (stl/css :actions-dropdown)}
(when is-you? (when is-you?
@ -910,12 +911,13 @@
(tr "dashboard.webhooks.create")]]) (tr "dashboard.webhooks.create")]])
(mf/defc webhook-actions (mf/defc webhook-actions
{::mf/wrap-props false} {::mf/props :obj
[{:keys [on-edit on-delete can-edit?]}] ::mf/private true}
[{:keys [on-edit on-delete can-edit]}]
(let [show? (mf/use-state false) (let [show? (mf/use-state false)
on-show (mf/use-fn #(reset! show? true)) on-show (mf/use-fn #(reset! show? true))
on-hide (mf/use-fn #(reset! show? false))] on-hide (mf/use-fn #(reset! show? false))]
(if can-edit? (if can-edit
[:* [:*
[:button {:class (stl/css :menu-btn) [:button {:class (stl/css :menu-btn)
:on-click on-show} :on-click on-show}
@ -948,7 +950,7 @@
creator-id (:profile-id webhook) creator-id (:profile-id webhook)
profile (mf/deref refs/profile) profile (mf/deref refs/profile)
user-id (:id profile) user-id (:id profile)
can-edit? (or (:can-edit permissions) can-edit (or (:can-edit permissions)
(= creator-id user-id)) (= creator-id user-id))
on-edit on-edit
(mf/use-fn (mf/use-fn
@ -1002,8 +1004,8 @@
[:div {:class (stl/css :table-field :actions)} [:div {:class (stl/css :table-field :actions)}
[:& webhook-actions [:& webhook-actions
{:on-edit on-edit {:on-edit on-edit
:can-edit? can-edit? :on-delete on-delete
:on-delete on-delete}]]])) :can-edit can-edit}]]]))
(mf/defc webhooks-list (mf/defc webhooks-list
{::mf/wrap-props false} {::mf/wrap-props false}
@ -1053,9 +1055,9 @@
stats (mf/deref refs/dashboard-team-stats) stats (mf/deref refs/dashboard-team-stats)
you-owner? (get-in team [:permissions :is-owner]) permissions (:permissions team)
you-admin? (get-in team [:permissions :is-admin]) can-edit (or (:is-owner permissions)
can-edit? (or you-owner? you-admin?) (:is-admin permissions))
on-image-click on-image-click
(mf/use-callback #(dom/click (mf/ref-val finput))) (mf/use-callback #(dom/click (mf/ref-val finput)))
@ -1086,13 +1088,13 @@
[:div {:class (stl/css :block-text)} [:div {:class (stl/css :block-text)}
(:name team)] (:name team)]
[:div {:class (stl/css :team-icon)} [:div {:class (stl/css :team-icon)}
(when can-edit? (when can-edit
[:button {:class (stl/css :update-overlay) [:button {:class (stl/css :update-overlay)
:on-click on-image-click} :on-click on-image-click}
image-icon]) image-icon])
[:img {:class (stl/css :team-image) [:img {:class (stl/css :team-image)
:src (cfg/resolve-team-photo-url team)}] :src (cfg/resolve-team-photo-url team)}]
(when can-edit? (when can-edit
[:& file-uploader {:accept "image/jpeg,image/png" [:& file-uploader {:accept "image/jpeg,image/png"
:multi false :multi false
:ref finput :ref finput

View file

@ -68,9 +68,8 @@
(mf/defc team-form-step-2 (mf/defc team-form-step-2
{::mf/props :obj} {::mf/props :obj}
[{:keys [name on-back go-to-team?]}] [{:keys [name on-back go-to-team?]}]
(let [initial (mf/use-memo (let [initial (mf/with-memo []
#(do {:role "editor" {:role "editor" :name name})
:name name}))
form (fm/use-form :schema schema:invite-form form (fm/use-form :schema schema:invite-form
:initial initial) :initial initial)

View file

@ -165,16 +165,16 @@
(let [layout (mf/deref refs/workspace-layout) (let [layout (mf/deref refs/workspace-layout)
wglobal (mf/deref refs/workspace-global) wglobal (mf/deref refs/workspace-global)
team (mf/deref refs/team)
file (mf/deref refs/workspace-file) file (mf/deref refs/workspace-file)
project (mf/deref refs/workspace-project) project (mf/deref refs/workspace-project)
team-id (:team-id project) team-id (:team-id project)
file-name (:name file) file-name (:name file)
permissions (:permissions team)
user-viewer? (not (dm/get-in file [:permissions :can-edit])) read-only? (mf/deref refs/workspace-read-only?)
read-only? (or (mf/deref refs/workspace-read-only?) read-only? (or read-only? (not (:can-edit permissions)))
user-viewer?)
file-ready* (mf/with-memo [file-id] file-ready* (mf/with-memo [file-id]
(make-file-ready-ref file-id)) (make-file-ready-ref file-id))
@ -214,7 +214,7 @@
[:& (mf/provider ctx/current-page-id) {:value page-id} [:& (mf/provider ctx/current-page-id) {:value page-id}
[:& (mf/provider ctx/components-v2) {:value components-v2?} [:& (mf/provider ctx/components-v2) {:value components-v2?}
[:& (mf/provider ctx/workspace-read-only?) {:value read-only?} [:& (mf/provider ctx/workspace-read-only?) {:value read-only?}
[:& (mf/provider ctx/user-viewer?) {:value user-viewer?} [:& (mf/provider ctx/team-permissions) {:value permissions}
[:section {:class (stl/css :workspace) [:section {:class (stl/css :workspace)
:style {:background-color background-color :style {:background-color background-color
:touch-action "none"}} :touch-action "none"}}

View file

@ -534,15 +534,17 @@
[:& menu-entry {:title (tr "workspace.assets.duplicate") [:& menu-entry {:title (tr "workspace.assets.duplicate")
:on-click do-duplicate}]])) :on-click do-duplicate}]]))
(mf/defc viewport-context-menu (mf/defc viewport-context-menu*
[{:keys [read-only?]}] {::mf/props :obj}
[]
(let [focus (mf/deref refs/workspace-focus-selected) (let [focus (mf/deref refs/workspace-focus-selected)
read-only? (mf/use-ctx ctx/workspace-read-only?)
do-paste #(st/emit! (dw/paste-from-clipboard)) do-paste #(st/emit! (dw/paste-from-clipboard))
do-hide-ui #(st/emit! (-> (dw/toggle-layout-flag :hide-ui) do-hide-ui #(st/emit! (-> (dw/toggle-layout-flag :hide-ui)
(vary-meta assoc ::ev/origin "workspace-context-menu"))) (vary-meta assoc ::ev/origin "workspace-context-menu")))
do-toggle-focus-mode #(st/emit! (dw/toggle-focus-mode))] do-toggle-focus-mode #(st/emit! (dw/toggle-focus-mode))]
[:* [:*
(when-not read-only? (when-not ^boolean read-only?
[:& menu-entry {:title (tr "workspace.shape.menu.paste") [:& menu-entry {:title (tr "workspace.shape.menu.paste")
:shortcut (sc/get-tooltip :paste) :shortcut (sc/get-tooltip :paste)
:on-click do-paste}]) :on-click do-paste}])
@ -640,6 +642,8 @@
:disabled (and (not single?) (not can-merge?))}]])) :disabled (and (not single?) (not can-merge?))}]]))
;; FIXME: optimize because it is rendered always
(mf/defc context-menu (mf/defc context-menu
[] []
(let [mdata (mf/deref menu-ref) (let [mdata (mf/deref menu-ref)
@ -648,10 +652,8 @@
dropdown-ref (mf/use-ref) dropdown-ref (mf/use-ref)
read-only? (mf/use-ctx ctx/workspace-read-only?)] read-only? (mf/use-ctx ctx/workspace-read-only?)]
(mf/use-effect (mf/with-effect [mdata]
(mf/deps mdata) (when-let [dropdown (mf/ref-val dropdown-ref)]
#(let [dropdown (mf/ref-val dropdown-ref)]
(when dropdown
(let [bounding-rect (dom/get-bounding-rect dropdown) (let [bounding-rect (dom/get-bounding-rect dropdown)
window-size (dom/get-window-size) window-size (dom/get-window-size)
delta-x (max (- (+ (:right bounding-rect) 250) (:width window-size)) 0) delta-x (max (- (+ (:right bounding-rect) 250) (:width window-size)) 0)
@ -659,7 +661,7 @@
new-style (str "top: " (- top delta-y) "px; " new-style (str "top: " (- top delta-y) "px; "
"left: " (- left delta-x) "px;")] "left: " (- left delta-x) "px;")]
(when (or (> delta-x 0) (> delta-y 0)) (when (or (> delta-x 0) (> delta-y 0))
(.setAttribute ^js dropdown "style" new-style)))))) (.setAttribute ^js dropdown "style" new-style)))))
[:& dropdown {:show (boolean mdata) [:& dropdown {:show (boolean mdata)
:on-close #(st/emit! dw/hide-context-menu)} :on-close #(st/emit! dw/hide-context-menu)}
@ -669,11 +671,11 @@
:on-context-menu prevent-default} :on-context-menu prevent-default}
[:ul {:class (stl/css :context-list)} [:ul {:class (stl/css :context-list)}
(if read-only? (if ^boolean read-only?
[:& viewport-context-menu {:mdata mdata :read-only? read-only?}] [:> viewport-context-menu* {:mdata mdata}]
(case (:kind mdata) (case (:kind mdata)
:shape [:& shape-context-menu {:mdata mdata}] :shape [:& shape-context-menu {:mdata mdata}]
:page [:& page-item-context-menu {:mdata mdata}] :page [:& page-item-context-menu {:mdata mdata}]
:grid-track [:& grid-track-context-menu {:mdata mdata}] :grid-track [:& grid-track-context-menu {:mdata mdata}]
:grid-cells [:& grid-cells-context-menu {:mdata mdata}] :grid-cells [:& grid-cells-context-menu {:mdata mdata}]
[:& viewport-context-menu {:mdata mdata}]))]]])) [:& viewport-context-menu* {:mdata mdata}]))]]]))

View file

@ -42,8 +42,9 @@
;; --- Header menu and submenus ;; --- Header menu and submenus
(mf/defc help-info-menu (mf/defc help-info-menu*
{::mf/wrap-props false {::mf/props :obj
::mf/private true
::mf/wrap [mf/memo]} ::mf/wrap [mf/memo]}
[{:keys [layout on-close]}] [{:keys [layout on-close]}]
(let [nav-to-helpc-center (let [nav-to-helpc-center
@ -172,8 +173,9 @@
[:span {:class (stl/css-case :feedback true [:span {:class (stl/css-case :feedback true
:item-name true)} (tr "labels.give-feedback")]])])) :item-name true)} (tr "labels.give-feedback")]])]))
(mf/defc preferences-menu (mf/defc preferences-menu*
{::mf/wrap-props false {::mf/props :obj
::mf/private true
::mf/wrap [mf/memo]} ::mf/wrap [mf/memo]}
[{:keys [layout profile toggle-flag on-close toggle-theme]}] [{:keys [layout profile toggle-flag on-close toggle-theme]}]
(let [show-nudge-options (mf/use-fn #(modal/show! {:type :nudge-option}))] (let [show-nudge-options (mf/use-fn #(modal/show! {:type :nudge-option}))]
@ -283,8 +285,9 @@
(for [sc (scd/split-sc (sc/get-tooltip :toggle-theme))] (for [sc (scd/split-sc (sc/get-tooltip :toggle-theme))]
[:span {:class (stl/css :shortcut-key) :key sc} sc])]]])) [:span {:class (stl/css :shortcut-key) :key sc} sc])]]]))
(mf/defc view-menu (mf/defc view-menu*
{::mf/wrap-props false {::mf/props :obj
::mf/private true
::mf/wrap [mf/memo]} ::mf/wrap [mf/memo]}
[{:keys [layout toggle-flag on-close]}] [{:keys [layout toggle-flag on-close]}]
(let [read-only? (mf/use-ctx ctx/workspace-read-only?) (let [read-only? (mf/use-ctx ctx/workspace-read-only?)
@ -412,13 +415,17 @@
(for [sc (scd/split-sc (sc/get-tooltip :hide-ui))] (for [sc (scd/split-sc (sc/get-tooltip :hide-ui))]
[:span {:class (stl/css :shortcut-key) :key sc} sc])]]])) [:span {:class (stl/css :shortcut-key) :key sc} sc])]]]))
(mf/defc edit-menu (mf/defc edit-menu*
{::mf/wrap-props false {::mf/props :obj
::mf/private true
::mf/wrap [mf/memo]} ::mf/wrap [mf/memo]}
[{:keys [on-close user-viewer?]}] [{:keys [on-close]}]
(let [select-all (mf/use-fn #(st/emit! (dw/select-all))) (let [select-all (mf/use-fn #(st/emit! (dw/select-all)))
undo (mf/use-fn #(st/emit! dwu/undo)) undo (mf/use-fn #(st/emit! dwu/undo))
redo (mf/use-fn #(st/emit! dwu/redo))] redo (mf/use-fn #(st/emit! dwu/redo))
perms (mf/use-ctx ctx/team-permissions)
can-edit (:can-edit perms)]
[:& dropdown-menu {:show true [:& dropdown-menu {:show true
:list-class (stl/css-case :sub-menu true :list-class (stl/css-case :sub-menu true
:edit true) :edit true)
@ -439,7 +446,7 @@
:key sc} :key sc}
sc])]] sc])]]
(when-not :user-viewer? user-viewer? (when can-edit
[:> dropdown-menu-item* {:class (stl/css :submenu-item) [:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click undo :on-click undo
:on-key-down (fn [event] :on-key-down (fn [event]
@ -453,7 +460,7 @@
:key sc} :key sc}
sc])]]) sc])]])
(when-not :user-viewer? user-viewer? (when can-edit
[:> dropdown-menu-item* {:class (stl/css :submenu-item) [:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click redo :on-click redo
:on-key-down (fn [event] :on-key-down (fn [event]
@ -468,9 +475,10 @@
:key sc} :key sc}
sc])]])])) sc])]])]))
(mf/defc file-menu (mf/defc file-menu*
{::mf/wrap-props false} {::mf/props :obj
[{:keys [on-close file user-viewer?]}] ::mf/private true}
[{:keys [on-close file can-edit]}]
(let [file-id (:id file) (let [file-id (:id file)
shared? (:is-shared file) shared? (:is-shared file)
@ -564,7 +572,7 @@
:id "file-menu-remove-shared"} :id "file-menu-remove-shared"}
[:span {:class (stl/css :item-name)} (tr "dashboard.unpublish-shared")]] [:span {:class (stl/css :item-name)} (tr "dashboard.unpublish-shared")]]
(when-not user-viewer? (when can-edit
[:> dropdown-menu-item* {:class (stl/css :submenu-item) [:> dropdown-menu-item* {:class (stl/css :submenu-item)
:on-click on-add-shared :on-click on-add-shared
:on-key-down on-add-shared-key-down :on-key-down on-add-shared-key-down
@ -613,8 +621,9 @@
[:span {:class (stl/css :item-name)} [:span {:class (stl/css :item-name)}
(tr "dashboard.export-frames")]])])) (tr "dashboard.export-frames")]])]))
(mf/defc plugins-menu (mf/defc plugins-menu*
{::mf/wrap-props false {::mf/props :obj
::mf/private true
::mf/wrap [mf/memo]} ::mf/wrap [mf/memo]}
[{:keys [open-plugins on-close]}] [{:keys [open-plugins on-close]}]
(when (features/active-feature? @st/state "plugins/runtime") (when (features/active-feature? @st/state "plugins/runtime")
@ -659,15 +668,13 @@
[:span {:class (stl/css :item-name)} name]])]))) [:span {:class (stl/css :item-name)} name]])])))
(mf/defc menu (mf/defc menu
{::mf/wrap-props false} {::mf/props :obj}
[{:keys [layout file profile]}] [{:keys [layout file profile]}]
(let [show-menu* (mf/use-state false) (let [show-menu* (mf/use-state false)
show-menu? (deref show-menu*) show-menu? (deref show-menu*)
sub-menu* (mf/use-state false) sub-menu* (mf/use-state false)
sub-menu (deref sub-menu*) sub-menu (deref sub-menu*)
user-viewer? (mf/use-ctx ctx/user-viewer?)
open-menu open-menu
(mf/use-fn (mf/use-fn
(fn [event] (fn [event]
@ -814,24 +821,21 @@
(case sub-menu (case sub-menu
:file :file
[:& file-menu [:> file-menu* {:file file
{:file file :on-close close-sub-menu}]
:on-close close-sub-menu
:user-viewer? user-viewer?}]
:edit :edit
[:& edit-menu [:> edit-menu*
{:on-close close-sub-menu {:on-close close-sub-menu}]
:user-viewer? user-viewer?}]
:view :view
[:& view-menu [:> view-menu*
{:layout layout {:layout layout
:toggle-flag toggle-flag :toggle-flag toggle-flag
:on-close close-sub-menu}] :on-close close-sub-menu}]
:preferences :preferences
[:& preferences-menu [:> preferences-menu*
{:layout layout {:layout layout
:profile profile :profile profile
:toggle-flag toggle-flag :toggle-flag toggle-flag
@ -839,12 +843,12 @@
:on-close close-sub-menu}] :on-close close-sub-menu}]
:plugins :plugins
[:& plugins-menu [:& plugins-menu*
{:open-plugins open-plugins-manager {:open-plugins open-plugins-manager
:on-close close-sub-menu}] :on-close close-sub-menu}]
:help-info :help-info
[:& help-info-menu [:& help-info-menu*
{:layout layout {:layout layout
:on-close close-sub-menu}] :on-close close-sub-menu}]

View file

@ -134,8 +134,7 @@
::mf/props :obj} ::mf/props :obj}
[{:keys [selected shapes shapes-with-children page-id file-id on-change-section on-expand]}] [{:keys [selected shapes shapes-with-children page-id file-id on-change-section on-expand]}]
(let [objects (mf/deref refs/workspace-page-objects) (let [objects (mf/deref refs/workspace-page-objects)
permissions (mf/use-ctx ctx/team-permissions)
user-viewer? (mf/use-ctx ctx/user-viewer?)
selected-shapes (into [] (keep (d/getf objects)) selected) selected-shapes (into [] (keep (d/getf objects)) selected)
first-selected-shape (first selected-shapes) first-selected-shape (first selected-shapes)
@ -176,10 +175,7 @@
tabs tabs
(if user-viewer? (if (:can-edit permissions)
#js [#js {:label (tr "workspace.options.inspect")
:id "inspect"
:content inspect-content}]
#js [#js {:label (tr "workspace.options.design") #js [#js {:label (tr "workspace.options.design")
:id "design" :id "design"
:content design-content} :content design-content}
@ -189,6 +185,9 @@
:content interactions-content} :content interactions-content}
#js {:label (tr "workspace.options.inspect") #js {:label (tr "workspace.options.inspect")
:id "inspect"
:content inspect-content}]
#js [#js {:label (tr "workspace.options.inspect")
:id "inspect" :id "inspect"
:content inspect-content}])] :content inspect-content}])]

View file

@ -34,7 +34,7 @@
id (:id page) id (:id page)
delete-fn (mf/use-fn (mf/deps id) #(st/emit! (dw/delete-page id))) delete-fn (mf/use-fn (mf/deps id) #(st/emit! (dw/delete-page id)))
navigate-fn (mf/use-fn (mf/deps id) #(st/emit! :interrupt (dw/go-to-page id))) navigate-fn (mf/use-fn (mf/deps id) #(st/emit! :interrupt (dw/go-to-page id)))
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) read-only? (mf/use-ctx ctx/workspace-read-only?)
on-delete on-delete
(mf/use-fn (mf/use-fn
@ -47,11 +47,11 @@
on-double-click on-double-click
(mf/use-fn (mf/use-fn
(mf/deps workspace-read-only?) (mf/deps read-only?)
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(when-not workspace-read-only? (when-not read-only?
(st/emit! (dw/start-rename-page-item id))))) (st/emit! (dw/start-rename-page-item id)))))
on-blur on-blur
@ -86,15 +86,15 @@
:data {:id id :data {:id id
:index index :index index
:name (:name page)} :name (:name page)}
:draggable? (not workspace-read-only?)) :draggable? (not read-only?))
on-context-menu on-context-menu
(mf/use-fn (mf/use-fn
(mf/deps id workspace-read-only?) (mf/deps id read-only?)
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(when-not workspace-read-only? (when-not read-only?
(let [position (dom/get-client-position event)] (let [position (dom/get-client-position event)]
(st/emit! (dw/show-page-item-context-menu (st/emit! (dw/show-page-item-context-menu
{:position position {:position position
@ -147,7 +147,7 @@
[:span {:class (stl/css :page-name) :data-testid "page-name"} [:span {:class (stl/css :page-name) :data-testid "page-name"}
(:name page)] (:name page)]
[:div {:class (stl/css :page-actions)} [:div {:class (stl/css :page-actions)}
(when (and deletable? (not workspace-read-only?)) (when (and deletable? (not read-only?))
[:button {:on-click on-delete} [:button {:on-click on-delete}
i/delete])]])]])) i/delete])]])]]))
@ -206,8 +206,7 @@
(st/emit! (dw/create-page {:file-id file-id :project-id project-id})) (st/emit! (dw/create-page {:file-id file-id :project-id project-id}))
(-> event dom/get-current-target dom/blur!))) (-> event dom/get-current-target dom/blur!)))
read-only? (mf/use-ctx ctx/workspace-read-only?) read-only? (mf/use-ctx ctx/workspace-read-only?)
user-viewer? (mf/use-ctx ctx/user-viewer?)] permissions (mf/use-ctx ctx/team-permissions)]
[:div {:class (stl/css :sitemap) [:div {:class (stl/css :sitemap)
:style #js {"--height" (str size "px")}} :style #js {"--height" (str size "px")}}
@ -220,7 +219,7 @@
:class (stl/css :title-spacing-sitemap)} :class (stl/css :title-spacing-sitemap)}
(if ^boolean read-only? (if ^boolean read-only?
(when (not ^boolean user-viewer?) (when ^boolean (:can-edit permissions)
[:& badge-notification {:is-focus true [:& badge-notification {:is-focus true
:size :small :size :small
:content (tr "labels.view-only")}]) :content (tr "labels.view-only")}])

View file

@ -95,8 +95,11 @@
vbox' (mf/use-debounce 100 vbox) vbox' (mf/use-debounce 100 vbox)
permissions (mf/use-ctx ctx/team-permissions)
read-only? (mf/use-ctx ctx/workspace-read-only?)
;; DEREFS ;; DEREFS
user-viewer? (mf/use-ctx ctx/user-viewer?)
drawing (mf/deref refs/workspace-drawing) drawing (mf/deref refs/workspace-drawing)
focus (mf/deref refs/workspace-focus-selected) focus (mf/deref refs/workspace-focus-selected)
@ -169,12 +172,11 @@
text-editing? (and edition (= :text (get-in base-objects [edition :type]))) text-editing? (and edition (= :text (get-in base-objects [edition :type])))
grid-editing? (and edition (ctl/grid-layout? base-objects edition)) grid-editing? (and edition (ctl/grid-layout? base-objects edition))
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
mode-inspect? (= options-mode :inspect) mode-inspect? (= options-mode :inspect)
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?) on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?)
on-context-menu (actions/on-context-menu hover hover-ids workspace-read-only?) on-context-menu (actions/on-context-menu hover hover-ids read-only?)
on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id drawing-path? base-objects edition drawing-tool z? workspace-read-only?) on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id drawing-path? base-objects edition drawing-tool z? read-only?)
comp-inst-ref (mf/use-ref false) comp-inst-ref (mf/use-ref false)
on-drag-enter (actions/on-drag-enter comp-inst-ref) on-drag-enter (actions/on-drag-enter comp-inst-ref)
@ -182,19 +184,19 @@
on-drag-end (actions/on-drag-over comp-inst-ref) on-drag-end (actions/on-drag-over comp-inst-ref)
on-drop (actions/on-drop file comp-inst-ref) on-drop (actions/on-drop file comp-inst-ref)
on-pointer-down (actions/on-pointer-down @hover selected edition drawing-tool text-editing? node-editing? grid-editing? on-pointer-down (actions/on-pointer-down @hover selected edition drawing-tool text-editing? node-editing? grid-editing?
drawing-path? create-comment? space? panning z? workspace-read-only?) drawing-path? create-comment? space? panning z? read-only?)
on-pointer-up (actions/on-pointer-up disable-paste) on-pointer-up (actions/on-pointer-up disable-paste)
on-pointer-enter (actions/on-pointer-enter in-viewport?) on-pointer-enter (actions/on-pointer-enter in-viewport?)
on-pointer-leave (actions/on-pointer-leave in-viewport?) on-pointer-leave (actions/on-pointer-leave in-viewport?)
on-pointer-move (actions/on-pointer-move move-stream) on-pointer-move (actions/on-pointer-move move-stream)
on-move-selected (actions/on-move-selected hover hover-ids selected space? z? workspace-read-only?) on-move-selected (actions/on-move-selected hover hover-ids selected space? z? read-only?)
on-menu-selected (actions/on-menu-selected hover hover-ids selected workspace-read-only?) on-menu-selected (actions/on-menu-selected hover hover-ids selected read-only?)
on-frame-enter (actions/on-frame-enter frame-hover) on-frame-enter (actions/on-frame-enter frame-hover)
on-frame-leave (actions/on-frame-leave frame-hover) on-frame-leave (actions/on-frame-leave frame-hover)
on-frame-select (actions/on-frame-select selected workspace-read-only?) on-frame-select (actions/on-frame-select selected read-only?)
disable-events? (contains? layout :comments) disable-events? (contains? layout :comments)
show-comments? (= drawing-tool :comments) show-comments? (= drawing-tool :comments)
@ -267,9 +269,9 @@
rule-area-size (/ rulers/ruler-area-size zoom)] rule-area-size (/ rulers/ruler-area-size zoom)]
(hooks/setup-dom-events zoom disable-paste in-viewport? workspace-read-only? drawing-tool drawing-path?) (hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool drawing-path?)
(hooks/setup-viewport-size vport viewport-ref) (hooks/setup-viewport-size vport viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? z? workspace-read-only?) (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? z? read-only?)
(hooks/setup-keyboard alt? mod? space? z? shift?) (hooks/setup-keyboard alt? mod? space? z? shift?)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover measure-hover (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover measure-hover
hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?) hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
@ -278,7 +280,7 @@
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox) (hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
[:div {:class (stl/css :viewport) :style #js {"--zoom" zoom} :data-testid "viewport"} [:div {:class (stl/css :viewport) :style #js {"--zoom" zoom} :data-testid "viewport"}
(when-not user-viewer? (when (:can-edit permissions)
[:& top-bar/top-bar {:layout layout}]) [:& top-bar/top-bar {:layout layout}])
[:div {:class (stl/css :viewport-overlays)} [:div {:class (stl/css :viewport-overlays)}
;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap ;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap
@ -288,7 +290,7 @@
[:div {:style {:pointer-events (when-not (dbg/enabled? :html-text) "none") [:div {:style {:pointer-events (when-not (dbg/enabled? :html-text) "none")
;; some opacity because to debug auto-width events will fill the screen ;; some opacity because to debug auto-width events will fill the screen
:opacity 0.6}} :opacity 0.6}}
(when-not workspace-read-only? (when (and (:can-edit permissions) (not read-only?))
[:& stvh/viewport-texts [:& stvh/viewport-texts
{:key (dm/str "texts-" page-id) {:key (dm/str "texts-" page-id)
:page-id page-id :page-id page-id

View file

@ -41,11 +41,11 @@
(defn on-pointer-down (defn on-pointer-down
[{:keys [id blocked hidden type]} selected edition drawing-tool text-editing? [{:keys [id blocked hidden type]} selected edition drawing-tool text-editing?
node-editing? grid-editing? drawing-path? create-comment? space? panning z? workspace-read-only?] node-editing? grid-editing? drawing-path? create-comment? space? panning z? read-only?]
(mf/use-callback (mf/use-callback
(mf/deps id blocked hidden type selected edition drawing-tool text-editing? (mf/deps id blocked hidden type selected edition drawing-tool text-editing?
node-editing? grid-editing? drawing-path? create-comment? @z? @space? node-editing? grid-editing? drawing-path? create-comment? @z? @space?
panning workspace-read-only?) panning read-only?)
(fn [bevent] (fn [bevent]
;; We need to handle editor related stuff here because ;; We need to handle editor related stuff here because
;; handling on editor dom node does not works properly. ;; handling on editor dom node does not works properly.
@ -101,24 +101,24 @@
(cond (cond
node-editing? node-editing?
;; Handle path node area selection ;; Handle path node area selection
(when-not workspace-read-only? (when-not read-only?
(st/emit! (dwdp/handle-area-selection shift?))) (st/emit! (dwdp/handle-area-selection shift?)))
drawing-tool drawing-tool
(when-not workspace-read-only? (when-not read-only?
(st/emit! (dd/start-drawing drawing-tool))) (st/emit! (dd/start-drawing drawing-tool)))
(or (not id) mod?) (or (not id) mod?)
(st/emit! (dw/handle-area-selection shift?)) (st/emit! (dw/handle-area-selection shift?))
(not drawing-tool) (not drawing-tool)
(when-not workspace-read-only? (when-not read-only?
(st/emit! (dw/start-move-selected id shift?))))))))))))) (st/emit! (dw/start-move-selected id shift?)))))))))))))
(defn on-move-selected (defn on-move-selected
[hover hover-ids selected space? z? workspace-read-only?] [hover hover-ids selected space? z? read-only?]
(mf/use-callback (mf/use-callback
(mf/deps @hover @hover-ids selected @space? @z? workspace-read-only?) (mf/deps @hover @hover-ids selected @space? @z? read-only?)
(fn [bevent] (fn [bevent]
(let [event (.-nativeEvent bevent) (let [event (.-nativeEvent bevent)
shift? (kbd/shift? event) shift? (kbd/shift? event)
@ -132,20 +132,20 @@
(dom/prevent-default bevent) (dom/prevent-default bevent)
(dom/stop-propagation bevent) (dom/stop-propagation bevent)
(when-not (or workspace-read-only? @z?) (when-not (or read-only? @z?)
(st/emit! (dw/start-move-selected)))))))) (st/emit! (dw/start-move-selected))))))))
(defn on-frame-select (defn on-frame-select
[selected workspace-read-only?] [selected read-only?]
(mf/use-callback (mf/use-callback
(mf/deps selected workspace-read-only?) (mf/deps selected read-only?)
(fn [event id] (fn [event id]
(let [shift? (kbd/shift? event) (let [shift? (kbd/shift? event)
selected? (contains? selected id) selected? (contains? selected id)
selected-drawtool (deref refs/selected-drawing-tool)] selected-drawtool (deref refs/selected-drawing-tool)]
(st/emit! (when (or shift? (not selected?)) (st/emit! (when (or shift? (not selected?))
(dw/select-shape id shift?)) (dw/select-shape id shift?))
(when (and (nil? selected-drawtool) (not shift?) (not workspace-read-only?)) (when (and (nil? selected-drawtool) (not shift?) (not read-only?))
(dw/start-move-selected))))))) (dw/start-move-selected)))))))
(defn on-frame-enter (defn on-frame-enter
@ -195,10 +195,10 @@
(st/emit! (dw/increase-zoom pt))))))))) (st/emit! (dw/increase-zoom pt)))))))))
(defn on-double-click (defn on-double-click
[hover hover-ids hover-top-frame-id drawing-path? objects edition drawing-tool z? workspace-read-only?] [hover hover-ids hover-top-frame-id drawing-path? objects edition drawing-tool z? read-only?]
(mf/use-callback (mf/use-callback
(mf/deps @hover @hover-ids @hover-top-frame-id drawing-path? edition drawing-tool @z? workspace-read-only?) (mf/deps @hover @hover-ids @hover-top-frame-id drawing-path? edition drawing-tool @z? read-only?)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(when-not @z? (when-not @z?
@ -223,7 +223,7 @@
(fn [] (fn []
(when (and (not drawing-path?) shape) (when (and (not drawing-path?) shape)
(cond (cond
(and editable? (not= id edition) (not workspace-read-only?)) (and editable? (not= id edition) (not read-only?))
(st/emit! (dw/select-shape id) (st/emit! (dw/select-shape id)
(dw/start-editing-selected)) (dw/start-editing-selected))
@ -231,16 +231,16 @@
(do (reset! hover selected-shape) (do (reset! hover selected-shape)
(st/emit! (dw/select-shape (:id selected-shape)))) (st/emit! (dw/select-shape (:id selected-shape))))
(and (not selected-shape) (some? grid-layout-id) (not workspace-read-only?)) (and (not selected-shape) (some? grid-layout-id) (not read-only?))
(st/emit! (dw/start-edition-mode grid-layout-id))))))))))) (st/emit! (dw/start-edition-mode grid-layout-id)))))))))))
(defn on-context-menu (defn on-context-menu
[hover hover-ids workspace-read-only?] [hover hover-ids read-only?]
(mf/use-fn (mf/use-fn
(mf/deps @hover @hover-ids workspace-read-only?) (mf/deps @hover @hover-ids read-only?)
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
;;(when-not workspace-read-only? ;;(when-not read-only?
(when (or (dom/class? (dom/get-target event) "viewport-controls") (when (or (dom/class? (dom/get-target event) "viewport-controls")
(dom/child? (dom/get-target event) (dom/query ".grid-layout-editor")) (dom/child? (dom/get-target event) (dom/query ".grid-layout-editor"))
(dom/class? (dom/get-target event) "viewport-selrect")) (dom/class? (dom/get-target event) "viewport-selrect"))
@ -248,20 +248,20 @@
;; Delayed callback because we need to wait to the previous context menu to be closed ;; Delayed callback because we need to wait to the previous context menu to be closed
(ts/schedule (ts/schedule
#(st/emit! #(st/emit!
(if (and (not workspace-read-only?) (some? @hover)) (if (and (not read-only?) (some? @hover))
(dw/show-shape-context-menu {:position position (dw/show-shape-context-menu {:position position
:shape @hover :shape @hover
:hover-ids @hover-ids}) :hover-ids @hover-ids})
(dw/show-context-menu {:position position}))))))))) (dw/show-context-menu {:position position})))))))))
(defn on-menu-selected (defn on-menu-selected
[hover hover-ids selected workspace-read-only?] [hover hover-ids selected read-only?]
(mf/use-callback (mf/use-callback
(mf/deps @hover @hover-ids selected workspace-read-only?) (mf/deps @hover @hover-ids selected read-only?)
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(when-not workspace-read-only? (when-not read-only?
(let [position (dom/get-client-position event)] (let [position (dom/get-client-position event)]
(st/emit! (dw/show-shape-context-menu {:position position :hover-ids @hover-ids}))))))) (st/emit! (dw/show-shape-context-menu {:position position :hover-ids @hover-ids})))))))
@ -538,14 +538,14 @@
(st/emit! (dwm/upload-media-workspace params)))))))) (st/emit! (dwm/upload-media-workspace params))))))))
(defn on-paste (defn on-paste
[disable-paste in-viewport? workspace-read-only?] [disable-paste in-viewport? read-only?]
(mf/use-fn (mf/use-fn
(mf/deps workspace-read-only?) (mf/deps read-only?)
(fn [event] (fn [event]
;; We disable the paste just after mouse-up of a middle button so ;; We disable the paste just after mouse-up of a middle button so
;; when panning won't paste the content into the workspace ;; when panning won't paste the content into the workspace
(let [tag-name (-> event dom/get-target dom/get-tag-name)] (let [tag-name (-> event dom/get-target dom/get-tag-name)]
(when (and (not (#{"INPUT" "TEXTAREA"} tag-name)) (when (and (not (#{"INPUT" "TEXTAREA"} tag-name))
(not @disable-paste) (not @disable-paste)
(not workspace-read-only?)) (not read-only?))
(st/emit! (dw/paste-from-event event @in-viewport?))))))) (st/emit! (dw/paste-from-event event @in-viewport?)))))))

View file

@ -39,7 +39,7 @@
:pages [] :pages []
:pages-index {}} :pages-index {}}
:workspace-libraries {} :workspace-libraries {}
:features/team #{"components/v2"}}) :features-team #{"components/v2"}})
(def ^:private idmap (atom {})) (def ^:private idmap (atom {}))

View file

@ -20,7 +20,7 @@
:current-page-id nil :current-page-id nil
:workspace-data nil :workspace-data nil
:workspace-libraries {} :workspace-libraries {}
:features/team #{"components/v2"}}) :features-team #{"components/v2"}})
(defn- on-error (defn- on-error
[cause] [cause]
@ -33,6 +33,7 @@
(let [state (-> initial-state (let [state (-> initial-state
(assoc :current-file-id (:id file) (assoc :current-file-id (:id file)
:current-page-id (cthf/current-page-id file) :current-page-id (cthf/current-page-id file)
:permissions {:can-edit true}
:workspace-file (dissoc file :data) :workspace-file (dissoc file :data)
:workspace-data (:data file))) :workspace-data (:data file)))
store (ptk/store {:state state :on-error on-error})] store (ptk/store {:state state :on-error on-error})]