mirror of
https://github.com/penpot/penpot.git
synced 2025-03-24 13:41:39 -05:00
✨ Add view mode to dashboard
This commit is contained in:
parent
c841ed6419
commit
cf150891df
36 changed files with 699 additions and 240 deletions
|
@ -89,7 +89,7 @@
|
|||
::sse/stream? true
|
||||
::sm/params schema:import-binfile}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id name project-id file] :as params}]
|
||||
(projects/check-read-permissions! pool profile-id project-id)
|
||||
(projects/check-edition-permissions! pool profile-id project-id)
|
||||
(let [cfg (-> cfg
|
||||
(assoc ::bf.v1/project-id project-id)
|
||||
(assoc ::bf.v1/profile-id profile-id)
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[app.common.features :as cfeat]
|
||||
[app.common.logging :as l]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.team :as tt]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
|
@ -20,6 +21,7 @@
|
|||
[app.loggers.audit :as audit]
|
||||
[app.main :as-alias main]
|
||||
[app.media :as media]
|
||||
[app.msgbus :as mbus]
|
||||
[app.rpc :as-alias rpc]
|
||||
[app.rpc.commands.profile :as profile]
|
||||
[app.rpc.doc :as-alias doc]
|
||||
|
@ -605,14 +607,8 @@
|
|||
nil)))
|
||||
|
||||
;; --- Mutation: Team Update Role
|
||||
|
||||
;; Temporarily disabled viewer role
|
||||
;; https://tree.taiga.io/project/penpot/issue/1083
|
||||
(def valid-roles
|
||||
#{:owner :admin :editor #_:viewer})
|
||||
|
||||
(def schema:role
|
||||
[::sm/one-of valid-roles])
|
||||
[::sm/one-of tt/valid-roles])
|
||||
|
||||
(defn role->params
|
||||
[role]
|
||||
|
@ -623,7 +619,7 @@
|
|||
:viewer {:is-owner false :is-admin false :can-edit false}))
|
||||
|
||||
(defn update-team-member-role
|
||||
[conn {:keys [profile-id team-id member-id role] :as params}]
|
||||
[{:keys [::db/conn ::mbus/msgbus]} {:keys [profile-id team-id member-id role] :as params}]
|
||||
;; We retrieve all team members instead of query the
|
||||
;; database for a single member. This is just for
|
||||
;; convenience, if this becomes a bottleneck or problematic,
|
||||
|
@ -631,7 +627,6 @@
|
|||
(let [perms (get-permissions conn profile-id team-id)
|
||||
members (get-team-members conn team-id)
|
||||
member (d/seek #(= member-id (:id %)) members)
|
||||
|
||||
is-owner? (:is-owner perms)
|
||||
is-admin? (:is-admin perms)]
|
||||
|
||||
|
@ -655,6 +650,13 @@
|
|||
(ex/raise :type :validation
|
||||
:code :cant-promote-to-owner))
|
||||
|
||||
(mbus/pub! msgbus
|
||||
:topic member-id
|
||||
:message {:type :team-permissions-change
|
||||
:subs-id member-id
|
||||
:team-id team-id
|
||||
:role role})
|
||||
|
||||
(let [params (role->params role)]
|
||||
;; Only allow single owner on team
|
||||
(when (= role :owner)
|
||||
|
@ -678,9 +680,8 @@
|
|||
(sv/defmethod ::update-team-member-role
|
||||
{::doc/added "1.17"
|
||||
::sm/params schema:update-team-member-role}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(update-team-member-role conn (assoc params :profile-id profile-id))))
|
||||
[cfg {:keys [::rpc/profile-id] :as params}]
|
||||
(db/tx-run! cfg update-team-member-role (assoc params :profile-id profile-id)))
|
||||
|
||||
;; --- Mutation: Delete Team Member
|
||||
|
||||
|
@ -724,6 +725,7 @@
|
|||
::sm/params schema:update-team-photo}
|
||||
[cfg {:keys [::rpc/profile-id file] :as params}]
|
||||
;; Validate incoming mime type
|
||||
|
||||
(media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"})
|
||||
(update-team-photo cfg (assoc params :profile-id profile-id)))
|
||||
|
||||
|
@ -1115,7 +1117,7 @@
|
|||
::sm/params schema:update-team-invitation-role}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id email role] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [perms (get-permissions conn profile-id team-id)]
|
||||
(let [perms (get-permissions conn profile-id team-id)]
|
||||
|
||||
(when-not (:is-admin perms)
|
||||
(ex/raise :type :validation
|
||||
|
@ -1124,6 +1126,7 @@
|
|||
(db/update! conn :team-invitation
|
||||
{:role (name role) :updated-at (dt/now)}
|
||||
{:team-id team-id :email-to (profile/clean-email email)})
|
||||
|
||||
nil)))
|
||||
|
||||
;; --- Mutation: Delete invitation
|
||||
|
|
10
common/src/app/common/types/team.cljc
Normal file
10
common/src/app/common/types/team.cljc
Normal file
|
@ -0,0 +1,10 @@
|
|||
;; 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.common.types.team)
|
||||
|
||||
(def valid-roles
|
||||
#{:owner :admin :editor :viewer})
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="200" xmlns="http://www.w3.org/2000/svg" height="160" fill="currentColor">
|
||||
<path d="M126.093 0c-10.089 5.695-21.391 13.947-30.08 21.449-11.486 9.914-20.725 19.51-23.851 23.889a1.385 1.385 0 0 0-.188.367c-3.947 11.582-7.764 23.208-11.84 34.745l13.335-5.442 20.971-8.016c.2-.076.38-.197.526-.353C120.526 39.6 163.623 10.96 189.505 0h-6.93C156.311 12.439 118.124 38.436 94 63.567l-8.043-7.62C98.262 41.808 130.799 12.528 160.197 0h-6.825c-28.401 13.838-57.638 40.392-69.486 53.986l-8.532-8.085c3.402-4.311 12.043-13.253 22.54-22.314C107.87 14.976 121.399 5.365 132.131 0ZM74.025 48.558l17.448 16.53-16.704 6.385-6.49-5.928Zm-27.381 57.948a1.446 1.446 0 0 0-.663.103l-34.643 14.136a1.437 1.437 0 0 0-.821 1.558V160h2.872v-35.3l25.13 15.59V160h2.872v-19.54l31.694-12.934V160h2.872v-34.207a1.438 1.438 0 0 0-.598-1.657L47.28 106.718a1.438 1.438 0 0 0-.636-.212Zm-1.561 3.569v25.776l-4.989 2.035-25.062-15.546Zm2.872.441 23.495 14.575-23.495 9.587Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 967 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="200" xmlns="http://www.w3.org/2000/svg" height="160" fill="currentColor">
|
||||
<path d="M20.429 0C8.728 18.551 5.333 41.776 13.482 62.939 21.937 84.9 42.8 104.491 79.116 114.242c16.034 4.305 34.081 6.105 48.632 11.788 14.358 5.609 25.245 14.695 28.17 33.97h2.886c-2.97-20.486-15.017-30.759-30.021-36.62-15.096-5.897-33.226-7.67-48.93-11.886-35.661-9.575-55.619-28.548-63.716-49.577C8.113 41.076 11.755 18.09 23.8 0Zm19.655 0C26.462 20.787 23.196 41.125 30.025 58.365c7.01 17.697 24.481 31.899 51.32 40.181 20.122 6.21 41.761 8.442 58.781 15.821 16.832 7.298 29.196 19.309 31.904 45.633h2.867c-2.735-27.335-16.141-40.657-33.638-48.244-17.627-7.642-39.324-9.835-59.074-15.929-26.297-8.115-42.909-21.835-49.514-38.51C26.07 40.655 29.32 20.81 43.478 0Zm19.51 0C47.031 13.736 40.256 31.375 44.382 47.702c4.301 17.018 20.451 32.157 53.042 39.511 26.565 5.995 48.332 11.2 63.806 21.463 15.276 10.133 24.625 25.1 26.897 51.324h2.859c-2.291-26.967-12.215-43.105-28.182-53.695-16.124-10.695-38.172-15.869-64.753-21.868-31.975-7.215-46.925-21.659-50.911-37.432C43.154 31.232 50.154 13.69 63.243.25c.063-.077.118-.161.163-.25Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,2 @@
|
|||
<svg width="200" xmlns="http://www.w3.org/2000/svg" height="160" fill="currentColor">
|
||||
<path d="M86.688 0v4.474c0 .014 0 .028.011.042.011.182.067.36.163.526.133.287.38.534.705.703l36.291 17.31a2.283 2.283 0 0 0 1.63.107l44.775-14.048c.708-.224 1.165-.77 1.145-1.369 0-.013 0-.027.011-.04V0h-3.66v6.808l-41.111 12.899V0h-3.659v19.279L90.352 3.712V0Zm-66.03 87.841c-7.576.065-14.52.466-20.658 1.132v2.894c24.59-2.746 64.081-.907 101.757 9.266 37.044 10.002 73.186 29.4 91.471 58.867h4.092c-18.575-31.072-56.205-51.277-94.373-61.583C74.112 90.631 44.4 87.636 20.658 87.841Zm-1.111 15.021c-7.353.127-13.95.625-19.547 1.418v2.89c.045 0 .086.008.131 0 19.39-2.848 53.254-1.852 86.537 6.167 32.319 7.786 64.558 23.007 82.716 46.662h4.278c-18.564-25.339-52.375-41.326-85.92-49.408-21.164-5.099-42.434-7.427-60.364-7.729a227.09 227.09 0 0 0-7.831 0Zm5.491 15.38c-9.232-.114-17.719.376-25.038 1.405v2.904c19.195-2.814 48.169-1.593 75.982 4.545 26.627 5.877 53.283 17.473 68.644 32.904h4.587c-15.783-17.069-44.145-29.468-72.241-35.668-17.77-3.922-35.926-5.892-51.934-6.09Z" /></svg>
|
After Width: | Height: | Size: 1 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg width="200" xmlns="http://www.w3.org/2000/svg" height="160" fill="currentColor">
|
||||
<path d="M9.08 0C2.012 12.672.983 25.825 5.093 38.045c4.265 12.678 13.931 24.333 27.724 33.889.128.089.269.153.417.189 10.995 2.859 22.067 5.392 33.045 8.314-5.086-10.922-10.136-21.867-15.212-32.796a1.403 1.403 0 0 0-.422-.53c-7.463-5.674-11.825-10.986-14.118-15.765-2.293-4.778-2.567-9.022-1.801-12.859 1.533-7.674 7.482-13.704 10.696-17.048.365-.38.991-.926 1.567-1.439h-4.004c-3.307 3.419-9.178 9.495-10.853 17.88-.884 4.422-.515 9.465 2.04 14.79 2.408 5.019 6.769 10.295 13.707 15.787l-6.341 8.705c-8.156-6.393-16.228-14.334-19.834-23.516-3.739-9.518-3.002-20.353 7.505-33.266l.318-.38h-3.48c-9.843 12.892-10.718 24.756-6.785 34.768 3.928 10.001 12.33 18.139 20.631 24.649L33.13 68.7C20.307 59.548 11.474 48.618 7.587 37.063 3.586 25.167 4.689 12.6 12.112.131L12.191 0H9.08Zm94.364 0c-4.278 16.424-3.662 33.117 2.326 46.638 6.185 13.966 18.129 24.458 35.756 27.479 15.277 2.618 26.956 4.677 36.058 8.155 8.428 3.221 14.622 7.623 19.733 14.88v-4.708c-5.057-6.067-11.176-10.097-18.806-13.013-9.478-3.622-21.268-5.672-36.55-8.292-16.843-2.887-27.858-12.661-33.678-25.804-5.732-12.943-6.324-29.24-1.964-45.335h-2.875Zm15.209 0c-2.745 11.298-4.41 24.868-.314 36.654 4.287 12.334 15.047 22.353 36.51 24.968 18.108 2.206 33.068 6.653 42.468 14.295v-3.71c-10.095-7.397-24.798-11.465-42.156-13.58-20.795-2.534-30.284-11.724-34.215-23.034-3.843-11.056-2.197-24.439.583-35.593h-2.876Zm14.514 0c-3.663 11.422-5.077 22.072-1.933 30.553 3.302 8.904 11.679 14.913 26.464 16.309 18.436 1.741 31.649 7.113 39.619 11.696v-3.372c-8.386-4.596-21.482-9.638-39.376-11.327-14.205-1.341-21.256-6.696-24.123-14.428-2.812-7.583-1.505-17.956 2.29-29.431h-2.941ZM49.56 50.787l7.322 15.784-5.26 7.219-15.913-3.994L49.56 50.787Zm49.609 70.716a1.277 1.277 0 0 0-.593.21l-26.172 17.388c-.469.334-.691.947-.557 1.532V160h2.669v-17.526l29.553 12.914V160h2.667v-4.782l23.434-15.569V160h2.668v-22.571c.188-.695-.14-1.431-.763-1.713l-32.292-14.112a1.248 1.248 0 0 0-.614-.101Zm1.448 3.551 28.029 12.247-23.372 15.527-4.657-2.035v-25.739Zm-2.667.436v24.138l-21.918-9.577L97.95 125.49Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -170,3 +170,26 @@
|
|||
(->> (rp/cmd! :create-team-access-request params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn change-team-permissions
|
||||
[team-id role]
|
||||
(ptk/reify ::change-team-permissions
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:teams team-id :permissions]
|
||||
(fn [permissions]
|
||||
(cond
|
||||
(= role :viewer)
|
||||
(assoc permissions :can-edit false :is-admin false :is-owner false)
|
||||
|
||||
(= role :editor)
|
||||
(assoc permissions :can-edit true :is-admin false :is-owner false)
|
||||
|
||||
(= role :admin)
|
||||
(assoc permissions :can-edit true :is-admin true :is-owner false)
|
||||
|
||||
(= role :owner)
|
||||
(assoc permissions :can-edit true :is-admin true :is-owner true)
|
||||
|
||||
:else
|
||||
permissions))))))
|
|
@ -12,17 +12,20 @@
|
|||
[app.common.files.helpers :as cfh]
|
||||
[app.common.logging :as log]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.team :as tt]
|
||||
[app.common.uri :as u]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.common :refer [handle-notification]]
|
||||
[app.main.data.common :as dc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.data.media :as di]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.websocket :as dws]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
|
@ -42,6 +45,7 @@
|
|||
|
||||
(declare fetch-projects)
|
||||
(declare fetch-team-members)
|
||||
(declare process-message)
|
||||
|
||||
(defn initialize
|
||||
[{:keys [id]}]
|
||||
|
@ -77,11 +81,10 @@
|
|||
(->> stream
|
||||
(rx/filter (ptk/type? ::dws/message))
|
||||
(rx/map deref)
|
||||
(rx/filter (fn [{:keys [subs-id type] :as msg}]
|
||||
(and (or (= subs-id uuid/zero)
|
||||
(= subs-id profile-id))
|
||||
(= :notification type))))
|
||||
(rx/map handle-notification))
|
||||
(rx/filter (fn [{:keys [subs-id] :as msg}]
|
||||
(or (= subs-id uuid/zero)
|
||||
(= subs-id profile-id))))
|
||||
(rx/map process-message))
|
||||
|
||||
;; Once the teams are fecthed, initialize features related
|
||||
;; to currently active team
|
||||
|
@ -477,10 +480,32 @@
|
|||
:team-id team-id}))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn handle-team-permissions-change
|
||||
[{:keys [role team-id]}]
|
||||
(dm/assert! (uuid? team-id))
|
||||
(dm/assert! (contains? tt/valid-roles role))
|
||||
|
||||
(let [msg (case role
|
||||
:viewer
|
||||
(tr "dashboard.permissions-change.viewer")
|
||||
|
||||
:editor
|
||||
(tr "dashboard.permissions-change.editor")
|
||||
|
||||
:admin
|
||||
(tr "dashboard.permissions-change.admin")
|
||||
|
||||
:owner
|
||||
(tr "dashboard.permissions-change.owner"))]
|
||||
|
||||
(st/emit! (ntf/info msg)
|
||||
(dc/change-team-permissions team-id role))))
|
||||
|
||||
(defn update-team-member-role
|
||||
[{:keys [role member-id] :as params}]
|
||||
(dm/assert! (uuid? member-id))
|
||||
(dm/assert! (keyword? role)) ; FIXME: validate proper role?
|
||||
(dm/assert! (contains? tt/valid-roles role))
|
||||
|
||||
(ptk/reify ::update-team-member-role
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -602,7 +627,7 @@
|
|||
(sm/check-email! email))
|
||||
|
||||
(dm/assert! (uuid? team-id))
|
||||
(dm/assert! (keyword? role)) ;; FIXME validate role
|
||||
(dm/assert! (contains? tt/valid-roles role))
|
||||
|
||||
(ptk/reify ::update-team-invitation-role
|
||||
IDeref
|
||||
|
@ -1203,3 +1228,14 @@
|
|||
(let [file (get-in state [:dashboard-files (first files)])]
|
||||
(rx/of (go-to-workspace file)))
|
||||
(rx/empty))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Notifications
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- process-message
|
||||
[{:keys [type] :as msg}]
|
||||
(case type
|
||||
:notification (dc/handle-notification msg)
|
||||
:team-permissions-change (handle-team-permissions-change msg)
|
||||
nil))
|
|
@ -65,6 +65,7 @@
|
|||
content-width (mf/use-state 0)
|
||||
project-id (:id project)
|
||||
team-id (:id team)
|
||||
you-viewer? (not (get-in team [:permissions :can-edit]))
|
||||
|
||||
dashboard-local (mf/deref refs/dashboard-local)
|
||||
file-menu-open? (:menu-open dashboard-local)
|
||||
|
@ -84,7 +85,10 @@
|
|||
|
||||
clear-selected-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)
|
||||
(not you-viewer?))]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [key1 (events/listen js/window "resize" on-resize)]
|
||||
|
@ -105,7 +109,7 @@
|
|||
:profile profile
|
||||
:default-project-id default-project-id}]
|
||||
|
||||
(when (contains? cf/flags :dashboard-templates-section)
|
||||
(when show-templates
|
||||
[:& templates-section {:profile profile
|
||||
:project-id project-id
|
||||
:team-id team-id
|
||||
|
@ -113,7 +117,7 @@
|
|||
:content-width @content-width}])]
|
||||
|
||||
:dashboard-fonts
|
||||
[:& fonts-page {:team team}]
|
||||
[:& fonts-page {:team team :you-viewer? you-viewer?}]
|
||||
|
||||
:dashboard-font-providers
|
||||
[:& font-providers-page {:team team}]
|
||||
|
@ -121,8 +125,8 @@
|
|||
:dashboard-files
|
||||
(when project
|
||||
[:*
|
||||
[:& files-section {:team team :project project}]
|
||||
(when (contains? cf/flags :dashboard-templates-section)
|
||||
[:& files-section {:team team :project project :you-viewer? you-viewer?}]
|
||||
(when show-templates
|
||||
[:& templates-section {:profile profile
|
||||
:team-id team-id
|
||||
:project-id project-id
|
||||
|
@ -134,7 +138,7 @@
|
|||
:search-term search-term}]
|
||||
|
||||
:dashboard-libraries
|
||||
[:& libraries-page {:team team}]
|
||||
[:& libraries-page {:team team :you-viewer? you-viewer?}]
|
||||
|
||||
:dashboard-team-members
|
||||
[:& team-members-page {:team team :profile profile :invite-email invite-email}]
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[app.main.data.events :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]]
|
||||
|
@ -55,7 +56,7 @@
|
|||
|
||||
(mf/defc file-menu
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [files show? on-edit on-menu-close top left navigate? origin parent-id]}]
|
||||
[{:keys [files show? on-edit on-menu-close top left navigate? origin parent-id you-viewer?]}]
|
||||
(assert (seq files) "missing `files` prop")
|
||||
(assert (boolean? show?) "missing `show?` prop")
|
||||
(assert (fn? on-edit) "missing `on-edit` prop")
|
||||
|
@ -73,7 +74,10 @@
|
|||
|
||||
current-team-id (mf/use-ctx ctx/current-team-id)
|
||||
teams (mf/use-state nil)
|
||||
current-team (get @teams current-team-id)
|
||||
default-team (-> (mf/deref refs/teams)
|
||||
(get current-team-id))
|
||||
|
||||
current-team (or (get @teams current-team-id) default-team)
|
||||
other-teams (remove #(= (:id %) current-team-id) (vals @teams))
|
||||
current-projects (remove #(= (:id %) (:project-id file))
|
||||
(:projects current-team))
|
||||
|
@ -237,11 +241,13 @@
|
|||
(:id sub-project))})})}]))
|
||||
|
||||
options (if multi?
|
||||
[{:option-name (tr "dashboard.duplicate-multi" file-count)
|
||||
:id "file-duplicate-multi"
|
||||
:option-handler on-duplicate
|
||||
:data-testid "duplicate-multi"}
|
||||
(when (or (seq current-projects) (seq other-teams))
|
||||
[(when-not you-viewer?
|
||||
{:option-name (tr "dashboard.duplicate-multi" file-count)
|
||||
:id "file-duplicate-multi"
|
||||
:option-handler on-duplicate
|
||||
:data-testid "duplicate-multi"})
|
||||
(when (and (or (seq current-projects) (seq other-teams))
|
||||
(not you-viewer?))
|
||||
{:option-name (tr "dashboard.move-to-multi" file-count)
|
||||
:id "file-move-multi"
|
||||
:sub-options sub-options
|
||||
|
@ -252,12 +258,14 @@
|
|||
{:option-name (tr "dashboard.export-standard-multi" file-count)
|
||||
:id "file-standard-export-multi"
|
||||
:option-handler on-export-standard-files}
|
||||
(when (:is-shared file)
|
||||
(when (and (:is-shared file)
|
||||
(not you-viewer?))
|
||||
{:option-name (tr "labels.unpublish-multi-files" file-count)
|
||||
:id "file-unpublish-multi"
|
||||
:option-handler on-del-shared
|
||||
:data-testid "file-del-shared"})
|
||||
(when (not is-lib-page?)
|
||||
(when (and (not is-lib-page?)
|
||||
(not you-viewer?))
|
||||
{:option-name :separator}
|
||||
{:option-name (tr "labels.delete-multi-files" file-count)
|
||||
:id "file-delete-multi"
|
||||
|
@ -267,22 +275,28 @@
|
|||
[{:option-name (tr "dashboard.open-in-new-tab")
|
||||
:id "file-open-new-tab"
|
||||
:option-handler on-new-tab}
|
||||
(when (not is-search-page?)
|
||||
(when (and (not is-search-page?)
|
||||
(not you-viewer?))
|
||||
{:option-name (tr "labels.rename")
|
||||
:id "file-rename"
|
||||
:option-handler on-edit
|
||||
:data-testid "file-rename"})
|
||||
(when (not is-search-page?)
|
||||
(when (and (not is-search-page?)
|
||||
(not you-viewer?))
|
||||
{:option-name (tr "dashboard.duplicate")
|
||||
:id "file-duplicate"
|
||||
:option-handler on-duplicate
|
||||
:data-testid "file-duplicate"})
|
||||
(when (and (not is-lib-page?) (not is-search-page?) (or (seq current-projects) (seq other-teams)))
|
||||
(when (and (not is-lib-page?)
|
||||
(not is-search-page?)
|
||||
(or (seq current-projects) (seq other-teams))
|
||||
(not you-viewer?))
|
||||
{:option-name (tr "dashboard.move-to")
|
||||
:id "file-move-to"
|
||||
:sub-options sub-options
|
||||
:data-testid "file-move-to"})
|
||||
(when (not is-search-page?)
|
||||
(when (and (not is-search-page?)
|
||||
(not you-viewer?))
|
||||
(if (:is-shared file)
|
||||
{:option-name (tr "dashboard.unpublish-shared")
|
||||
:id "file-del-shared"
|
||||
|
@ -301,7 +315,7 @@
|
|||
:id "file-download-standard"
|
||||
:option-handler on-export-standard-files
|
||||
:data-testid "download-standard-file"}
|
||||
(when (and (not is-lib-page?) (not is-search-page?))
|
||||
(when (and (not is-lib-page?) (not is-search-page?) (not you-viewer?))
|
||||
{:option-name :separator}
|
||||
{:option-name (tr "labels.delete")
|
||||
:id "file-delete"
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
|
||||
[app.main.ui.dashboard.project-menu :refer [project-menu]]
|
||||
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -28,7 +29,7 @@
|
|||
(i/icon-xref :menu (stl/css :menu-icon)))
|
||||
|
||||
(mf/defc header
|
||||
[{:keys [project create-fn] :as props}]
|
||||
[{:keys [project create-fn you-viewer?] :as props}]
|
||||
(let [local (mf/use-state
|
||||
{:menu-open false
|
||||
:edition false})
|
||||
|
@ -71,7 +72,8 @@
|
|||
[:div#dashboard-drafts-title {:class (stl/css :dashboard-title)}
|
||||
[:h1 (tr "labels.drafts")]]
|
||||
|
||||
(if (:edition @local)
|
||||
(if (and (:edition @local)
|
||||
(not you-viewer?))
|
||||
[:& inline-edition
|
||||
{:content (:name project)
|
||||
:on-end (fn [name]
|
||||
|
@ -86,23 +88,16 @@
|
|||
:id (:id project)}
|
||||
(:name project)]]))
|
||||
|
||||
[:& project-menu {:project project
|
||||
:show? (:menu-open @local)
|
||||
:left (- (:x (:menu-pos @local)) 180)
|
||||
:top (:y (:menu-pos @local))
|
||||
:on-edit on-edit
|
||||
:on-menu-close on-menu-close
|
||||
:on-import on-import}]
|
||||
|
||||
[:div {:class (stl/css :dashboard-header-actions)}
|
||||
[:a {:class (stl/css :btn-secondary :btn-small :new-file)
|
||||
:tab-index "0"
|
||||
:on-click on-create-click
|
||||
:data-testid "new-file"
|
||||
:on-key-down (fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-create-click event)))}
|
||||
(tr "dashboard.new-file")]
|
||||
(when-not you-viewer?
|
||||
[:a {:class (stl/css :btn-secondary :btn-small :new-file)
|
||||
:tab-index "0"
|
||||
:on-click on-create-click
|
||||
:data-testid "new-file"
|
||||
:on-key-down (fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-create-click event)))}
|
||||
(tr "dashboard.new-file")])
|
||||
|
||||
(when-not (:is-default project)
|
||||
[:> pin-button*
|
||||
|
@ -111,19 +106,30 @@
|
|||
:on-click toggle-pin
|
||||
:on-key-down (fn [event] (when (kbd/enter? event) (toggle-pin event)))}])
|
||||
|
||||
[:div {:class (stl/css :icon)
|
||||
:tab-index "0"
|
||||
:on-click on-menu-click
|
||||
:title (tr "dashboard.options")
|
||||
:on-key-down (fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-menu-click event)))}
|
||||
menu-icon]]]))
|
||||
(when-not you-viewer?
|
||||
[:div {:class (stl/css :icon)
|
||||
:tab-index "0"
|
||||
:on-click on-menu-click
|
||||
:title (tr "dashboard.options")
|
||||
:on-key-down (fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-menu-click event)))}
|
||||
menu-icon])
|
||||
|
||||
(when-not you-viewer?
|
||||
[:& project-menu {:project project
|
||||
:show? (:menu-open @local)
|
||||
:left (- (:x (:menu-pos @local)) 180)
|
||||
:top (:y (:menu-pos @local))
|
||||
:on-edit on-edit
|
||||
:on-menu-close on-menu-close
|
||||
:on-import on-import}])]]))
|
||||
|
||||
(mf/defc files-section
|
||||
[{:keys [project team] :as props}]
|
||||
[{:keys [project team you-viewer?] :as props}]
|
||||
(let [files-map (mf/deref refs/dashboard-files)
|
||||
project-id (:id project)
|
||||
is-draft-proyect (:is-default project)
|
||||
|
||||
[rowref limit] (hooks/use-dynamic-grid-item-width)
|
||||
|
||||
|
@ -132,6 +138,9 @@
|
|||
(filter #(= project-id (:project-id %)))
|
||||
(sort-by :modified-at)
|
||||
(reverse)))
|
||||
file-count (or (count files) 0)
|
||||
empty-state-viewer (and you-viewer?
|
||||
(= 0 file-count))
|
||||
|
||||
on-file-created
|
||||
(mf/use-fn
|
||||
|
@ -164,12 +173,23 @@
|
|||
[:*
|
||||
[:& header {:team team
|
||||
:project project
|
||||
:you-viewer? you-viewer?
|
||||
:create-fn create-file}]
|
||||
[:section {:class (stl/css :dashboard-container :no-bg)
|
||||
:ref rowref}
|
||||
[:& grid {:project project
|
||||
:files files
|
||||
:origin :files
|
||||
:create-fn create-file
|
||||
:limit limit}]]]))
|
||||
(if empty-state-viewer
|
||||
[:> empty-placeholder* {:title (if is-draft-proyect
|
||||
(tr "dashboard.empty-placeholder-drafts-title")
|
||||
(tr "dashboard.empty-placeholder-files-title"))
|
||||
:class (stl/css :placeholder-placement)
|
||||
:type 1
|
||||
:subtitle (if is-draft-proyect
|
||||
(tr "dashboard.empty-placeholder-drafts-subtitle")
|
||||
(tr "dashboard.empty-placeholder-files-subtitle"))}]
|
||||
[:& grid {:project project
|
||||
:files files
|
||||
:you-viewer? you-viewer?
|
||||
:origin :files
|
||||
:create-fn create-file
|
||||
:limit limit}])]]))
|
||||
|
||||
|
|
|
@ -35,3 +35,7 @@
|
|||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
|
||||
.placeholder-placement {
|
||||
margin: $s-16 $s-32;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu-a11y]]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.notifications.context-notification :refer [context-notification]]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -269,7 +270,7 @@
|
|||
{::mf/props :obj
|
||||
::mf/private true
|
||||
::mf/memo true}
|
||||
[{:keys [font-id variants]}]
|
||||
[{:keys [font-id variants you-viewer?]}]
|
||||
(let [font (first variants)
|
||||
|
||||
menu-open* (mf/use-state false)
|
||||
|
@ -364,11 +365,12 @@
|
|||
:key (dm/str id)}
|
||||
[:span {:class (stl/css :label)}
|
||||
[:& font-variant-display-name {:variant item}]]
|
||||
[:span
|
||||
{:class (stl/css :icon :close)
|
||||
:data-id (dm/str id)
|
||||
:on-click on-delete-variant}
|
||||
i/add]])]
|
||||
(when-not you-viewer?
|
||||
[:span
|
||||
{:class (stl/css :icon :close)
|
||||
:data-id (dm/str id)
|
||||
:on-click on-delete-variant}
|
||||
i/add])])]
|
||||
|
||||
(if ^boolean edition?
|
||||
[:div {:class (stl/css :table-field :options)}
|
||||
|
@ -382,19 +384,19 @@
|
|||
:on-click on-cancel}
|
||||
i/close]]
|
||||
|
||||
[:div {:class (stl/css :table-field :options)}
|
||||
[:span {:class (stl/css :icon)
|
||||
:on-click on-menu-open}
|
||||
i/menu]
|
||||
(when-not you-viewer? [:div {:class (stl/css :table-field :options)}
|
||||
[:span {:class (stl/css :icon)
|
||||
:on-click on-menu-open}
|
||||
i/menu]
|
||||
|
||||
[:& installed-font-context-menu
|
||||
{:on-close on-menu-close
|
||||
:is-open menu-open?
|
||||
:on-delete on-delete-font
|
||||
:on-edit on-edit}]])]))
|
||||
[:& installed-font-context-menu
|
||||
{:on-close on-menu-close
|
||||
:is-open menu-open?
|
||||
:on-delete on-delete-font
|
||||
:on-edit on-edit}]]))]))
|
||||
|
||||
(mf/defc installed-fonts
|
||||
[{:keys [fonts] :as props}]
|
||||
[{:keys [fonts you-viewer?] :as props}]
|
||||
(let [sterm (mf/use-state "")
|
||||
|
||||
matches?
|
||||
|
@ -407,23 +409,24 @@
|
|||
(reset! sterm (str/lower val)))))]
|
||||
|
||||
[:div {:class (stl/css :dashboard-installed-fonts)}
|
||||
[:h3 (tr "labels.installed-fonts")]
|
||||
[:div {:class (stl/css :installed-fonts-header)}
|
||||
[:div {:class (stl/css :table-field :family)} (tr "labels.font-family")]
|
||||
[:div {:class (stl/css :table-field :variants)} (tr "labels.font-variants")]
|
||||
[:div {:class (stl/css :table-field :search-input)}
|
||||
[:input {:placeholder (tr "labels.search-font")
|
||||
:default-value ""
|
||||
:on-change on-change}]]]
|
||||
|
||||
(cond
|
||||
(seq fonts)
|
||||
(for [[font-id variants] (->> (vals fonts)
|
||||
(filter matches?)
|
||||
(group-by :font-id))]
|
||||
[:& installed-font {:key (dm/str font-id "-installed")
|
||||
:font-id font-id
|
||||
:variants variants}])
|
||||
[:*
|
||||
[:h3 (tr "labels.installed-fonts")]
|
||||
[:div {:class (stl/css :installed-fonts-header)}
|
||||
[:div {:class (stl/css :table-field :family)} (tr "labels.font-family")]
|
||||
[:div {:class (stl/css :table-field :variants)} (tr "labels.font-variants")]
|
||||
[:div {:class (stl/css :table-field :search-input)}
|
||||
[:input {:placeholder (tr "labels.search-font")
|
||||
:default-value ""
|
||||
:on-change on-change}]]]
|
||||
(for [[font-id variants] (->> (vals fonts)
|
||||
(filter matches?)
|
||||
(group-by :font-id))]
|
||||
[:& installed-font {:key (dm/str font-id "-installed")
|
||||
:font-id font-id
|
||||
:you-viewer? you-viewer?
|
||||
:variants variants}])]
|
||||
|
||||
(nil? fonts)
|
||||
[:div {:class (stl/css :fonts-placeholder)}
|
||||
|
@ -431,18 +434,24 @@
|
|||
[:div {:class (stl/css :label)} (tr "dashboard.loading-fonts")]]
|
||||
|
||||
:else
|
||||
[:div {:class (stl/css :fonts-placeholder)}
|
||||
[:div {:class (stl/css :icon)} i/text]
|
||||
[:div {:class (stl/css :label)} (tr "dashboard.fonts.empty-placeholder")]])]))
|
||||
(if you-viewer?
|
||||
[:> 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 :icon)} i/text]
|
||||
[:div {:class (stl/css :label)} (tr "dashboard.fonts.empty-placeholder")]]))]))
|
||||
|
||||
(mf/defc fonts-page
|
||||
[{:keys [team] :as props}]
|
||||
[{:keys [team you-viewer?] :as props}]
|
||||
(let [fonts (mf/deref refs/dashboard-fonts)]
|
||||
[:*
|
||||
[:& header {:team team :section :fonts}]
|
||||
[:section {:class (stl/css :dashboard-container :dashboard-fonts)}
|
||||
[:& uploaded-fonts {:team team :installed-fonts fonts}]
|
||||
[:& installed-fonts {:team team :fonts fonts}]]]))
|
||||
(when-not you-viewer?
|
||||
[:& uploaded-fonts {:team team :installed-fonts fonts}])
|
||||
[:& installed-fonts {:team team :fonts fonts :you-viewer? you-viewer?}]]]))
|
||||
|
||||
(mf/defc font-providers-page
|
||||
[{:keys [team] :as props}]
|
||||
|
|
|
@ -128,6 +128,7 @@
|
|||
flex-wrap: wrap;
|
||||
flex-grow: 1;
|
||||
padding-left: $s-16;
|
||||
gap: $s-6;
|
||||
|
||||
.variant {
|
||||
display: flex;
|
||||
|
@ -135,13 +136,13 @@
|
|||
align-items: center;
|
||||
padding: $s-8 $s-12;
|
||||
cursor: pointer;
|
||||
|
||||
gap: $s-4;
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: $s-16;
|
||||
width: $s-16;
|
||||
margin-left: $s-6;
|
||||
align-items: center;
|
||||
svg {
|
||||
fill: none;
|
||||
width: $s-12;
|
||||
|
@ -163,8 +164,6 @@
|
|||
.variant {
|
||||
background-color: var(--color-background-quaternary);
|
||||
border-radius: $br-8;
|
||||
margin-right: $s-4;
|
||||
padding-right: $s-4;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
|
||||
(mf/defc grid-item-thumbnail
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [file-id revn thumbnail-id background-color]}]
|
||||
[{:keys [file-id revn thumbnail-id background-color you-viewer?]}]
|
||||
(let [container (mf/use-ref)
|
||||
visible? (h/use-visible container :once? true)]
|
||||
|
||||
|
@ -94,10 +94,12 @@
|
|||
(when visible?
|
||||
(if thumbnail-id
|
||||
[:img {:class (stl/css :grid-item-thumbnail-image)
|
||||
:draggable (dm/str (not you-viewer?))
|
||||
:src (cf/resolve-media thumbnail-id)
|
||||
:loading "lazy"
|
||||
:decoding "async"}]
|
||||
[:> loader* {:class (stl/css :grid-loader)
|
||||
:draggable (dm/str (not you-viewer?))
|
||||
:overlay true
|
||||
:title (tr "labels.loading")}]))]))
|
||||
|
||||
|
@ -231,7 +233,7 @@
|
|||
|
||||
(mf/defc grid-item
|
||||
{:wrap [mf/memo]}
|
||||
[{:keys [file origin library-view?] :as props}]
|
||||
[{:keys [file origin library-view? you-viewer?] :as props}]
|
||||
(let [file-id (:id file)
|
||||
|
||||
;; FIXME: this breaks react hooks rule, hooks should never to
|
||||
|
@ -274,33 +276,34 @@
|
|||
|
||||
on-drag-start
|
||||
(mf/use-fn
|
||||
(mf/deps selected-files)
|
||||
(mf/deps selected-files you-viewer?)
|
||||
(fn [event]
|
||||
(st/emit! (dd/hide-file-menu))
|
||||
(let [offset (dom/get-offset-position (.-nativeEvent event))
|
||||
(when-not you-viewer?
|
||||
(let [offset (dom/get-offset-position (.-nativeEvent event))
|
||||
|
||||
select-current? (not (contains? selected-files (:id file)))
|
||||
select-current? (not (contains? selected-files (:id file)))
|
||||
|
||||
item-el (mf/ref-val node-ref)
|
||||
counter-el (create-counter-element
|
||||
item-el
|
||||
(if select-current?
|
||||
1
|
||||
(count selected-files)))]
|
||||
(when select-current?
|
||||
(st/emit! (dd/clear-selected-files))
|
||||
(st/emit! (dd/toggle-file-select file)))
|
||||
item-el (mf/ref-val node-ref)
|
||||
counter-el (create-counter-element
|
||||
item-el
|
||||
(if select-current?
|
||||
1
|
||||
(count selected-files)))]
|
||||
(when select-current?
|
||||
(st/emit! (dd/clear-selected-files))
|
||||
(st/emit! (dd/toggle-file-select file)))
|
||||
|
||||
(dnd/set-data! event "penpot/files" "dummy")
|
||||
(dnd/set-allowed-effect! event "move")
|
||||
(dnd/set-data! event "penpot/files" "dummy")
|
||||
(dnd/set-allowed-effect! event "move")
|
||||
|
||||
;; set-drag-image requires that the element is rendered and
|
||||
;; visible to the user at the moment of creating the ghost
|
||||
;; image (to make a snapshot), but you may remove it right
|
||||
;; afterwards, in the next render cycle.
|
||||
(dom/append-child! item-el counter-el)
|
||||
(dnd/set-drag-image! event item-el (:x offset) (:y offset))
|
||||
(ts/raf #(.removeChild ^js item-el counter-el)))))
|
||||
(dom/append-child! item-el counter-el)
|
||||
(dnd/set-drag-image! event item-el (:x offset) (:y offset))
|
||||
(ts/raf #(.removeChild ^js item-el counter-el))))))
|
||||
|
||||
on-menu-click
|
||||
(mf/use-fn
|
||||
|
@ -351,13 +354,12 @@
|
|||
(on-select event)) ;; TODO Fix this
|
||||
)))]
|
||||
|
||||
[:li
|
||||
{:class (stl/css-case :grid-item true :project-th true :library library-view?)}
|
||||
[:li {:class (stl/css-case :grid-item true :project-th true :library library-view?)}
|
||||
[:button
|
||||
{:class (stl/css-case :selected selected? :library library-view?)
|
||||
:ref node-ref
|
||||
:title (:name file)
|
||||
:draggable true
|
||||
:draggable (dm/str (not you-viewer?))
|
||||
:on-click on-select
|
||||
:on-key-down handle-key-down
|
||||
:on-double-click on-navigate
|
||||
|
@ -370,6 +372,7 @@
|
|||
[:& grid-item-library {:file file}]
|
||||
[:& grid-item-thumbnail
|
||||
{:file-id (:id file)
|
||||
:you-viewer? you-viewer?
|
||||
:revn (:revn file)
|
||||
:thumbnail-id (:thumbnail-id file)
|
||||
:background-color (dm/get-in file [:data :options :background])}])
|
||||
|
@ -405,6 +408,7 @@
|
|||
:show? (:menu-open dashboard-local)
|
||||
:left (+ 24 (:x (:menu-pos dashboard-local)))
|
||||
:top (:y (:menu-pos dashboard-local))
|
||||
:you-viewer? you-viewer?
|
||||
:navigate? true
|
||||
:on-edit on-edit
|
||||
:on-menu-close on-menu-close
|
||||
|
@ -412,7 +416,7 @@
|
|||
:parent-id (str file-id "-action-menu")}]])]]]]]))
|
||||
|
||||
(mf/defc grid
|
||||
[{:keys [files project origin limit library-view? create-fn] :as props}]
|
||||
[{:keys [files project origin limit library-view? create-fn you-viewer?] :as props}]
|
||||
(let [dragging? (mf/use-state false)
|
||||
project-id (:id project)
|
||||
node-ref (mf/use-var nil)
|
||||
|
@ -429,11 +433,12 @@
|
|||
on-drag-enter
|
||||
(mf/use-fn
|
||||
(fn [e]
|
||||
(when (and (not (dnd/has-type? e "penpot/files"))
|
||||
(or (dnd/has-type? e "Files")
|
||||
(dnd/has-type? e "application/x-moz-file")))
|
||||
(dom/prevent-default e)
|
||||
(reset! dragging? true))))
|
||||
(when-not you-viewer?
|
||||
(when (and (not (dnd/has-type? e "penpot/files"))
|
||||
(or (dnd/has-type? e "Files")
|
||||
(dnd/has-type? e "application/x-moz-file")))
|
||||
(dom/prevent-default e)
|
||||
(reset! dragging? true)))))
|
||||
|
||||
on-drag-over
|
||||
(mf/use-fn
|
||||
|
@ -459,6 +464,7 @@
|
|||
(import-files (.-files (.-dataTransfer e))))))]
|
||||
|
||||
[:div {:class (stl/css :dashboard-grid)
|
||||
:dragabble (dm/str (not you-viewer?))
|
||||
:on-drag-enter on-drag-enter
|
||||
:on-drag-over on-drag-over
|
||||
:on-drag-leave on-drag-leave
|
||||
|
@ -480,21 +486,22 @@
|
|||
:key (:id item)
|
||||
:navigate? true
|
||||
:origin origin
|
||||
:you-viewer? you-viewer?
|
||||
:library-view? library-view?}])])
|
||||
|
||||
:else
|
||||
[:& empty-placeholder
|
||||
{:limit limit
|
||||
:you-viewer? you-viewer?
|
||||
:create-fn create-fn
|
||||
:origin origin}])]))
|
||||
|
||||
(mf/defc line-grid-row
|
||||
[{:keys [files selected-files dragging? limit] :as props}]
|
||||
[{:keys [files selected-files dragging? limit you-viewer?] :as props}]
|
||||
(let [elements limit
|
||||
limit (if dragging? (dec limit) limit)]
|
||||
[:ul
|
||||
{:class (stl/css :grid-row :no-wrap)
|
||||
:style {:grid-template-columns (dm/str "repeat(" elements ", 1fr)")}}
|
||||
[:ul {:class (stl/css :grid-row :no-wrap)
|
||||
:style {:grid-template-columns (dm/str "repeat(" elements ", 1fr)")}}
|
||||
|
||||
(when dragging?
|
||||
[:li {:class (stl/css :grid-item :dragged)}])
|
||||
|
@ -504,11 +511,12 @@
|
|||
{:id (:id item)
|
||||
:file item
|
||||
:selected-files selected-files
|
||||
:you-viewer? you-viewer?
|
||||
:key (:id item)
|
||||
:navigate? false}])]))
|
||||
|
||||
(mf/defc line-grid
|
||||
[{:keys [project team files limit create-fn] :as props}]
|
||||
[{:keys [project team files limit create-fn you-viewer?] :as props}]
|
||||
(let [dragging? (mf/use-state false)
|
||||
project-id (:id project)
|
||||
team-id (:id team)
|
||||
|
@ -527,22 +535,23 @@
|
|||
|
||||
on-drag-enter
|
||||
(mf/use-fn
|
||||
(mf/deps selected-project)
|
||||
(mf/deps selected-project you-viewer?)
|
||||
(fn [e]
|
||||
(cond
|
||||
(dnd/has-type? e "penpot/files")
|
||||
(do
|
||||
(dom/prevent-default e)
|
||||
(when-not (or (dnd/from-child? e)
|
||||
(dnd/broken-event? e))
|
||||
(when (not= selected-project project-id)
|
||||
(reset! dragging? true))))
|
||||
(when-not you-viewer?
|
||||
(cond
|
||||
(dnd/has-type? e "penpot/files")
|
||||
(do
|
||||
(dom/prevent-default e)
|
||||
(when-not (or (dnd/from-child? e)
|
||||
(dnd/broken-event? e))
|
||||
(when (not= selected-project project-id)
|
||||
(reset! dragging? true))))
|
||||
|
||||
(or (dnd/has-type? e "Files")
|
||||
(dnd/has-type? e "application/x-moz-file"))
|
||||
(do
|
||||
(dom/prevent-default e)
|
||||
(reset! dragging? true)))))
|
||||
(or (dnd/has-type? e "Files")
|
||||
(dnd/has-type? e "application/x-moz-file"))
|
||||
(do
|
||||
(dom/prevent-default e)
|
||||
(reset! dragging? true))))))
|
||||
|
||||
on-drag-over
|
||||
(mf/use-fn
|
||||
|
@ -586,6 +595,7 @@
|
|||
(import-files (.-files (.-dataTransfer e)))))))]
|
||||
|
||||
[:div {:class (stl/css :dashboard-grid)
|
||||
:dragabble (dm/str (not you-viewer?))
|
||||
:on-drag-enter on-drag-enter
|
||||
:on-drag-over on-drag-over
|
||||
:on-drag-leave on-drag-leave
|
||||
|
@ -599,10 +609,12 @@
|
|||
:team-id team-id
|
||||
:selected-files selected-files
|
||||
:dragging? @dragging?
|
||||
:you-viewer? you-viewer?
|
||||
:limit limit}]
|
||||
|
||||
:else
|
||||
[:& empty-placeholder
|
||||
{:dragging? @dragging?
|
||||
:limit limit
|
||||
:you-viewer? you-viewer?
|
||||
:create-fn create-fn}])]))
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc libraries-page
|
||||
[{:keys [team] :as props}]
|
||||
[{:keys [team you-viewer?] :as props}]
|
||||
(let [files-map (mf/deref refs/dashboard-shared-files)
|
||||
projects (mf/deref refs/dashboard-projects)
|
||||
|
||||
|
@ -56,5 +56,6 @@
|
|||
:project default-project
|
||||
:origin :libraries
|
||||
:limit limit
|
||||
:you-viewer? you-viewer?
|
||||
:library-view? components-v2}]]]))
|
||||
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
(ns app.main.ui.dashboard.placeholder
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc empty-placeholder
|
||||
[{:keys [dragging? limit origin create-fn]}]
|
||||
[{:keys [dragging? limit origin create-fn you-viewer?]}]
|
||||
(let [on-click
|
||||
(mf/use-fn
|
||||
(mf/deps create-fn)
|
||||
|
@ -27,14 +28,17 @@
|
|||
[:li {:class (stl/css :grid-item :grid-empty-placeholder :dragged)}]]
|
||||
|
||||
(= :libraries origin)
|
||||
[:div {:class (stl/css :grid-empty-placeholder :libs)
|
||||
:data-testid "empty-placeholder"}
|
||||
[:div {:class (stl/css :text)}
|
||||
[:> i18n/tr-html* {:content (tr "dashboard.empty-placeholder-drafts")}]]]
|
||||
[:> empty-placeholder* {:title (tr "dashboard.empty-placeholder-libraries-title")
|
||||
:type 2
|
||||
:subtitle (when you-viewer? (tr "dashboard.empty-placeholder-libraries-subtitle-viewer-role"))
|
||||
:class (stl/css :empty-placeholder-libraries)}
|
||||
(when-not you-viewer?
|
||||
[:> i18n/tr-html* {:content (tr "dashboard.empty-placeholder-drafts")
|
||||
:class (stl/css :placeholder-markdown)
|
||||
:tag-name "span"}])]
|
||||
|
||||
:else
|
||||
[:div
|
||||
{:class (stl/css :grid-empty-placeholder)}
|
||||
[:div {:class (stl/css :grid-empty-placeholder)}
|
||||
[:button {:class (stl/css :create-new)
|
||||
:on-click on-click}
|
||||
i/add]])))
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
@use "common/refactor/common-refactor.scss" as *;
|
||||
@use "./grid.scss" as g;
|
||||
@use "../ds/typography.scss" as t;
|
||||
|
||||
.grid-empty-placeholder {
|
||||
border-radius: $br-12;
|
||||
|
@ -89,3 +90,14 @@
|
|||
font-size: $fs-16;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.placeholder-markdown {
|
||||
@include t.use-typography("body-large");
|
||||
a {
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.empty-placeholder-libraries {
|
||||
margin: $s-16;
|
||||
}
|
||||
|
|
|
@ -118,9 +118,6 @@
|
|||
:data-testid "project-delete"})]]
|
||||
|
||||
[:*
|
||||
[:& udi/import-form {:ref file-input
|
||||
:project-id (:id project)
|
||||
:on-finish-import on-finish-import}]
|
||||
[:& context-menu-a11y
|
||||
{:on-close on-menu-close
|
||||
:show show?
|
||||
|
@ -129,5 +126,8 @@
|
|||
:top top
|
||||
:left left
|
||||
:options options
|
||||
:workspace false}]]))
|
||||
:workspace false}]
|
||||
[:& udi/import-form {:ref file-input
|
||||
:project-id (:id project)
|
||||
:on-finish-import on-finish-import}]]))
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
|
||||
[app.main.ui.dashboard.project-menu :refer [project-menu]]
|
||||
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -44,15 +45,16 @@
|
|||
|
||||
(mf/defc header
|
||||
{::mf/wrap [mf/memo]}
|
||||
[]
|
||||
[{:keys [you-viewer?]}]
|
||||
(let [on-click (mf/use-fn #(st/emit! (dd/create-project)))]
|
||||
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
|
||||
[:div#dashboard-projects-title {:class (stl/css :dashboard-title)}
|
||||
[:h1 (tr "dashboard.projects-title")]]
|
||||
[:button {:class (stl/css :btn-secondary :btn-small)
|
||||
:on-click on-click
|
||||
:data-testid "new-project-button"}
|
||||
(tr "dashboard.new-project")]]))
|
||||
(when-not you-viewer?
|
||||
[:button {:class (stl/css :btn-secondary :btn-small)
|
||||
:on-click on-click
|
||||
:data-testid "new-project-button"}
|
||||
(tr "dashboard.new-project")])]))
|
||||
|
||||
(mf/defc team-hero*
|
||||
{::mf/wrap [mf/memo]
|
||||
|
@ -98,11 +100,14 @@
|
|||
(l/derived :builtin-templates st/state))
|
||||
|
||||
(mf/defc project-item
|
||||
[{:keys [project first? team files] :as props}]
|
||||
[{:keys [project first? team files you-viewer?] :as props}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
file-count (or (:count project) 0)
|
||||
project-id (:id project)
|
||||
is-draft-proyect (:is-default project)
|
||||
team-id (:id team)
|
||||
empty-state-viewer (and you-viewer?
|
||||
(= 0 file-count))
|
||||
|
||||
dstate (mf/deref refs/dashboard-local)
|
||||
edit-id (:project-for-edit dstate)
|
||||
|
@ -198,7 +203,6 @@
|
|||
(when (kbd/enter? event)
|
||||
(on-create-click event))))
|
||||
|
||||
|
||||
handle-menu-click
|
||||
(mf/use-callback
|
||||
(mf/deps on-menu-click)
|
||||
|
@ -220,20 +224,13 @@
|
|||
:title (if (:is-default project)
|
||||
(tr "labels.drafts")
|
||||
(:name project))
|
||||
:on-context-menu on-menu-click}
|
||||
:on-context-menu (when-not you-viewer? on-menu-click)}
|
||||
(if (:is-default project)
|
||||
(tr "labels.drafts")
|
||||
(:name project))])
|
||||
|
||||
[:div {:class (stl/css :info-wrapper)}
|
||||
[:& project-menu
|
||||
{:project project
|
||||
:show? (:menu-open @local)
|
||||
:left (+ 24 (:x (:menu-pos @local)))
|
||||
:top (:y (:menu-pos @local))
|
||||
:on-edit on-edit-open
|
||||
:on-menu-close on-menu-close
|
||||
:on-import on-import}]
|
||||
|
||||
|
||||
;; We group these two spans under a div to avoid having extra space between them.
|
||||
[:div
|
||||
|
@ -248,29 +245,51 @@
|
|||
(when-not (:is-default project)
|
||||
[:> pin-button* {:class (stl/css :pin-button) :is-pinned (:is-pinned project) :on-click toggle-pin :tab-index 0}])
|
||||
|
||||
[:button {:class (stl/css :add-file-btn)
|
||||
:on-click on-create-click
|
||||
:title (tr "dashboard.new-file")
|
||||
:aria-label (tr "dashboard.new-file")
|
||||
:data-testid "project-new-file"
|
||||
:on-key-down handle-create-click}
|
||||
add-icon]
|
||||
(when-not you-viewer?
|
||||
[:button {:class (stl/css :add-file-btn)
|
||||
:on-click on-create-click
|
||||
:title (tr "dashboard.new-file")
|
||||
:aria-label (tr "dashboard.new-file")
|
||||
:data-testid "project-new-file"
|
||||
:on-key-down handle-create-click}
|
||||
add-icon])
|
||||
|
||||
[:button {:class (stl/css :options-btn)
|
||||
:on-click on-menu-click
|
||||
:title (tr "dashboard.options")
|
||||
:aria-label (tr "dashboard.options")
|
||||
:data-testid "project-options"
|
||||
:on-key-down handle-menu-click}
|
||||
menu-icon]]]]]
|
||||
(when-not you-viewer?
|
||||
[:button {:class (stl/css :options-btn)
|
||||
:on-click on-menu-click
|
||||
:title (tr "dashboard.options")
|
||||
:aria-label (tr "dashboard.options")
|
||||
:data-testid "project-options"
|
||||
:on-key-down handle-menu-click}
|
||||
menu-icon])]
|
||||
(when-not you-viewer?
|
||||
[:& project-menu
|
||||
{:project project
|
||||
:show? (:menu-open @local)
|
||||
:left (+ 24 (:x (:menu-pos @local)))
|
||||
:top (:y (:menu-pos @local))
|
||||
:on-edit on-edit-open
|
||||
:on-menu-close on-menu-close
|
||||
:on-import on-import}])]]]
|
||||
|
||||
[:div {:class (stl/css :grid-container) :ref rowref}
|
||||
[:& line-grid
|
||||
{:project project
|
||||
:team team
|
||||
:files files
|
||||
:create-fn create-file
|
||||
:limit limit}]]
|
||||
(if empty-state-viewer
|
||||
[:> empty-placeholder* {:title (if is-draft-proyect
|
||||
(tr "dashboard.empty-placeholder-drafts-title")
|
||||
(tr "dashboard.empty-placeholder-files-title"))
|
||||
:class (stl/css :placeholder-placement)
|
||||
:type 1
|
||||
:subtitle (if is-draft-proyect
|
||||
(tr "dashboard.empty-placeholder-drafts-subtitle")
|
||||
(tr "dashboard.empty-placeholder-files-subtitle"))}]
|
||||
|
||||
[:& line-grid
|
||||
{:project project
|
||||
:team team
|
||||
:files files
|
||||
:create-fn create-file
|
||||
:you-viewer? you-viewer?
|
||||
:limit limit}])]
|
||||
|
||||
(when (and (> limit 0)
|
||||
(> file-count limit))
|
||||
|
@ -295,6 +314,7 @@
|
|||
recent-map (mf/deref recent-files-ref)
|
||||
you-owner? (get-in team [:permissions :is-owner])
|
||||
you-admin? (get-in team [:permissions :is-admin])
|
||||
you-viewer? (not (get-in team [:permissions :can-edit]))
|
||||
can-invite? (or you-owner? you-admin?)
|
||||
|
||||
show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true))
|
||||
|
@ -327,7 +347,7 @@
|
|||
|
||||
(when (seq projects)
|
||||
[:*
|
||||
[:& header]
|
||||
[:& header {:you-viewer? you-viewer?}]
|
||||
[:div {:class (stl/css :projects-container)}
|
||||
[:*
|
||||
(when (and show-team-hero?
|
||||
|
@ -350,5 +370,6 @@
|
|||
[:& project-item {:project project
|
||||
:team team
|
||||
:files files
|
||||
:you-viewer? you-viewer?
|
||||
:first? (= project (first projects))
|
||||
:key id}]))]]]])))
|
||||
|
|
|
@ -128,6 +128,10 @@
|
|||
padding: 0 $s-4;
|
||||
}
|
||||
|
||||
.placeholder-placement {
|
||||
margin: $s-16 $s-32;
|
||||
}
|
||||
|
||||
.show-more {
|
||||
--show-more-color: var(--button-secondary-foreground-color-rest);
|
||||
@include buttonStyle;
|
||||
|
|
|
@ -118,13 +118,10 @@
|
|||
|
||||
(defn get-available-roles
|
||||
[permissions]
|
||||
(->> [{:value "editor" :label (tr "labels.editor")}
|
||||
(->> [{:value "viewer" :label (tr "labels.viewer")}
|
||||
{:value "editor" :label (tr "labels.editor")}
|
||||
(when (:is-admin permissions)
|
||||
{:value "admin" :label (tr "labels.admin")})
|
||||
;; Temporarily disabled viewer roles
|
||||
;; https://tree.taiga.io/project/penpot/issue/1083
|
||||
;; {:value "viewer" :label (tr "labels.viewer")}
|
||||
]
|
||||
{:value "admin" :label (tr "labels.admin")})]
|
||||
(filterv identity)))
|
||||
|
||||
(def ^:private schema:invite-member-form
|
||||
|
@ -146,7 +143,7 @@
|
|||
team-id (:id team)
|
||||
|
||||
initial (mf/with-memo [team-id]
|
||||
{:role "editor" :team-id team-id})
|
||||
{:role "viewer" :team-id team-id})
|
||||
|
||||
form (fm/use-form :schema schema:invite-member-form
|
||||
:initial initial)
|
||||
|
@ -256,7 +253,7 @@
|
|||
|
||||
(mf/defc rol-info
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [member team on-set-admin on-set-editor on-set-owner profile]}]
|
||||
[{:keys [member team on-set-admin on-set-editor on-set-owner on-set-viewer profile]}]
|
||||
(let [member-is-owner? (:is-owner member)
|
||||
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?)))
|
||||
|
@ -294,12 +291,12 @@
|
|||
[:li {:on-click on-set-editor
|
||||
:class (stl/css :rol-dropdown-item)}
|
||||
(tr "labels.editor")]
|
||||
;; Temporarily disabled viewer role
|
||||
;; https://tree.taiga.io/project/penpot/issue/1083
|
||||
;; [:li {:on-click set-viewer} (tr "labels.viewer")]
|
||||
[:li {:on-click on-set-viewer
|
||||
:class (stl/css :rol-dropdown-item)}
|
||||
(tr "labels.viewer")]
|
||||
(when you-owner?
|
||||
[:li {:on-click (partial on-set-owner member)
|
||||
:class (:stl/css :rol-dropdown-item)}
|
||||
:class (stl/css :rol-dropdown-item)}
|
||||
(tr "labels.owner")])]]]))
|
||||
|
||||
(mf/defc member-actions
|
||||
|
@ -344,6 +341,7 @@
|
|||
(let [member-id (:id member)
|
||||
on-set-admin (mf/use-fn (mf/deps member-id) (partial set-role! member-id :admin))
|
||||
on-set-editor (mf/use-fn (mf/deps member-id) (partial set-role! member-id :editor))
|
||||
on-set-viewer (mf/use-fn (mf/deps member-id) (partial set-role! member-id :viewer))
|
||||
owner? (dm/get-in team [:permissions :is-owner])
|
||||
|
||||
on-set-owner
|
||||
|
@ -459,6 +457,7 @@
|
|||
:team team
|
||||
:on-set-admin on-set-admin
|
||||
:on-set-editor on-set-editor
|
||||
:on-set-viewer on-set-viewer
|
||||
:on-set-owner on-set-owner
|
||||
:profile profile}]]
|
||||
|
||||
|
@ -567,7 +566,11 @@
|
|||
[:li {:data-role "editor"
|
||||
:class (stl/css :rol-dropdown-item)
|
||||
:on-click on-change'}
|
||||
(tr "labels.editor")]]]]))
|
||||
(tr "labels.editor")]
|
||||
[:li {:data-role "viewer"
|
||||
:class (stl/css :rol-dropdown-item)
|
||||
:on-click on-change'}
|
||||
(tr "labels.viewer")]]]]))
|
||||
|
||||
(mf/defc invitation-actions
|
||||
{::mf/wrap-props false}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
|
||||
[app.main.ui.ds.notifications.toast :refer [toast*]]
|
||||
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.ds.storybook :as sb]
|
||||
[app.util.i18n :as i18n]))
|
||||
|
@ -32,6 +33,7 @@
|
|||
:Icon icon*
|
||||
:IconButton icon-button*
|
||||
:Input input*
|
||||
:EmptyPlaceholder empty-placeholder*
|
||||
:Loader loader*
|
||||
:RawSvg raw-svg*
|
||||
:Select select*
|
||||
|
|
|
@ -10,5 +10,8 @@
|
|||
$sz-16: px2rem(16);
|
||||
$sz-32: px2rem(32);
|
||||
$sz-36: px2rem(36);
|
||||
$sz-160: px2rem(160);
|
||||
$sz-200: px2rem(200);
|
||||
$sz-224: px2rem(224);
|
||||
$sz-400: px2rem(400);
|
||||
$sz-964: px2rem(964);
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
(def ^:svg-id marketing-layers "marketing-layers")
|
||||
(def ^:svg-id penpot-logo "penpot-logo")
|
||||
(def ^:svg-id penpot-logo-icon "penpot-logo-icon")
|
||||
(def ^:svg-id empty-placeholder-1-left "empty-placeholder-1-left")
|
||||
(def ^:svg-id empty-placeholder-1-right "empty-placeholder-1-right")
|
||||
(def ^:svg-id empty-placeholder-2-left "empty-placeholder-2-left")
|
||||
(def ^:svg-id empty-placeholder-2-right "empty-placeholder-2-right")
|
||||
|
||||
(def raw-svg-list "A collection of all raw SVG assets" (collect-raw-svgs))
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def levels (set '("info" "warning" "error" "success")))
|
||||
|
||||
(def ^:private icons-by-level
|
||||
{"info" i/info
|
||||
"warning" i/msg-neutral
|
||||
|
|
40
frontend/src/app/main/ui/ds/product/empty_placeholder.cljs
Normal file
40
frontend/src/app/main/ui/ds/product/empty_placeholder.cljs
Normal file
|
@ -0,0 +1,40 @@
|
|||
;; 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.ds.product.empty-placeholder
|
||||
(:require-macros
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]]
|
||||
[app.main.ui.ds.foundations.typography :as t]
|
||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema:empty-placeholder
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:title :string]
|
||||
[:subtitle {:optional true} [:maybe :string]]
|
||||
[:type {:optional true} [:maybe [:enum 1 2]]]])
|
||||
|
||||
(mf/defc empty-placeholder*
|
||||
{::mf/props :obj
|
||||
::mf/schema schema:empty-placeholder}
|
||||
[{:keys [class title subtitle type children] :rest props}]
|
||||
|
||||
(let [class (dm/str class " " (stl/css :empty-placeholder))
|
||||
props (mf/spread-props props {:class class})
|
||||
type (or type 1)
|
||||
decoration-type (dm/str "empty-placeholder-" (str type))]
|
||||
[:> "div" props
|
||||
[:> raw-svg* {:id (dm/str decoration-type "-left") :class (stl/css :svg-decor)}]
|
||||
[:div {:class (stl/css :text-wrapper)}
|
||||
[:> text* {:as "span" :typography t/title-medium :class (stl/css :placeholder-title)} title]
|
||||
(when subtitle
|
||||
[:> text* {:as "span" :typography t/body-large} subtitle])
|
||||
children]
|
||||
[:> raw-svg* {:id (dm/str decoration-type "-right") :class (stl/css :svg-decor)}]]))
|
38
frontend/src/app/main/ui/ds/product/empty_placeholder.scss
Normal file
38
frontend/src/app/main/ui/ds/product/empty_placeholder.scss
Normal file
|
@ -0,0 +1,38 @@
|
|||
// 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
|
||||
|
||||
@use "../_sizes.scss" as *;
|
||||
@use "../_borders.scss" as *;
|
||||
|
||||
.empty-placeholder {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
place-content: center;
|
||||
background: none;
|
||||
color: var(--color-foreground-secondary);
|
||||
height: $sz-160;
|
||||
max-width: $sz-964;
|
||||
border-radius: $br-8;
|
||||
border: $b-1 solid var(--color-background-quaternary);
|
||||
}
|
||||
|
||||
.text-wrapper {
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
max-width: $sz-400;
|
||||
}
|
||||
|
||||
.placeholder-title {
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.svg-decor {
|
||||
height: $sz-160;
|
||||
width: $sz-200;
|
||||
color: var(--color-background-quaternary);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import * as React from "react";
|
||||
import Components from "@target/components";
|
||||
|
||||
const { EmptyPlaceholder } = Components;
|
||||
|
||||
export default {
|
||||
title: "Product/EmptyPlaceholder",
|
||||
component: EmptyPlaceholder,
|
||||
argTypes: {
|
||||
title: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
type: {
|
||||
control: "radio",
|
||||
options: [1, 2],
|
||||
},
|
||||
},
|
||||
args: {
|
||||
type: 1,
|
||||
title: "Lorem ipsum",
|
||||
subtitle:
|
||||
"dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
},
|
||||
render: ({ ...args }) => <EmptyPlaceholder {...args} />,
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
|
||||
export const AlternativeDecoration = {
|
||||
args: {
|
||||
type: 2,
|
||||
},
|
||||
};
|
|
@ -61,14 +61,16 @@
|
|||
|
||||
(defn- get-available-roles
|
||||
[]
|
||||
[{:value "editor" :label (tr "labels.editor")}
|
||||
[{:value "viewer" :label (tr "labels.viewer")}
|
||||
{:value "editor" :label (tr "labels.editor")}
|
||||
{:value "admin" :label (tr "labels.admin")}])
|
||||
|
||||
(mf/defc team-form-step-2
|
||||
{::mf/props :obj}
|
||||
[{:keys [name on-back go-to-team?]}]
|
||||
(let [initial (mf/with-memo []
|
||||
{:role "editor" :name name})
|
||||
(let [initial (mf/use-memo
|
||||
#(do {:role "viewer"
|
||||
:name name}))
|
||||
|
||||
form (fm/use-form :schema schema:invite-form
|
||||
:initial initial)
|
||||
|
|
|
@ -181,9 +181,9 @@
|
|||
:id "prototype"
|
||||
:content interactions-content}
|
||||
|
||||
#js {:label (tr "workspace.options.inspect")
|
||||
:id "inspect"
|
||||
:content inspect-content}]]
|
||||
#js {:label (tr "workspace.options.inspect")
|
||||
:id "inspect"
|
||||
:content inspect-content}]]
|
||||
|
||||
[:div {:class (stl/css :tool-window)}
|
||||
[:> tab-switcher* {:tabs tabs
|
||||
|
|
|
@ -62,8 +62,6 @@
|
|||
on-change
|
||||
(mf/use-fn
|
||||
(fn [new-color old-color from-picker?]
|
||||
(prn "new-color" new-color)
|
||||
(prn "old-color" old-color)
|
||||
(let [old-color (-> old-color
|
||||
(dissoc :name :path)
|
||||
(d/without-nils))
|
||||
|
|
|
@ -205,7 +205,8 @@
|
|||
(fn [event]
|
||||
(st/emit! (dw/create-page {:file-id file-id :project-id project-id}))
|
||||
(-> 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?)]
|
||||
|
||||
[:div {:class (stl/css :sitemap)
|
||||
:style #js {"--height" (str size "px")}}
|
||||
|
@ -218,9 +219,10 @@
|
|||
:class (stl/css :title-spacing-sitemap)}
|
||||
|
||||
(if ^boolean read-only?
|
||||
[:& badge-notification {:is-focus true
|
||||
:size :small
|
||||
:content (tr "labels.view-only")}]
|
||||
(when (not ^boolean user-viewer?)
|
||||
[:& badge-notification {:is-focus true
|
||||
:size :small
|
||||
:content (tr "labels.view-only")}])
|
||||
[:button {:class (stl/css :add-page)
|
||||
:on-click on-create}
|
||||
i/add])]
|
||||
|
|
|
@ -439,6 +439,55 @@ msgstr ""
|
|||
"Files added to Libraries will appear here. Try sharing your files or add "
|
||||
"from our [Libraries & templates](https://penpot.app/libraries-templates)."
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#: src/app/main/ui/dashboard/placeholder.cljs
|
||||
msgid "dashboard.empty-placeholder-libraries-title"
|
||||
msgstr "No libraries yet."
|
||||
|
||||
#: src/app/main/ui/dashboard/placeholder.cljs
|
||||
#, markdown
|
||||
msgid "dashboard.empty-placeholder-libraries-subtitle"
|
||||
msgstr ""
|
||||
"Files added to Libraries will appear here. Try sharing your files or add "
|
||||
"from our [Libraries & templates](https://penpot.app/libraries-templates)."
|
||||
|
||||
#: src/app/main/ui/dashboard/placeholder.cljs
|
||||
msgid "dashboard.empty-placeholder-libraries-subtitle-viewer-role"
|
||||
msgstr "Files added to Libraries will appear here."
|
||||
|
||||
#: src/app/main/ui/dashboard
|
||||
msgid "dashboard.empty-placeholder-drafts-title"
|
||||
msgstr "No drafts yet."
|
||||
|
||||
#: src/app/main/ui/dashboard
|
||||
msgid "dashboard.empty-placeholder-drafts-subtitle"
|
||||
msgstr "Once a project member creates a draft, it will be displayed here."
|
||||
|
||||
#: src/app/main/ui/dashboard
|
||||
msgid "dashboard.empty-placeholder-files-title"
|
||||
msgstr "No files yet."
|
||||
|
||||
#: src/app/main/ui/dashboard
|
||||
msgid "dashboard.empty-placeholder-files-subtitle"
|
||||
msgstr "Once a project member creates a file, it will be displayed here."
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#: src/app/main/ui/dashboard/file_menu.cljs:249
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Download %s Penpot files (.penpot)"
|
||||
|
@ -541,6 +590,14 @@ msgstr "Dismiss all"
|
|||
msgid "dashboard.fonts.empty-placeholder"
|
||||
msgstr "Custom fonts you upload will appear here."
|
||||
|
||||
#: src/app/main/ui/dashboard/fonts.cljs:436
|
||||
msgid "dashboard.fonts.empty-placeholder-viewer"
|
||||
msgstr "No custom fonts yet."
|
||||
|
||||
#: src/app/main/ui/dashboard/fonts.cljs:436
|
||||
msgid "dashboard.fonts.empty-placeholder-viewer-sub"
|
||||
msgstr "Once a project member uploads a custom font, it will be displayed here."
|
||||
|
||||
#: src/app/main/ui/dashboard/fonts.cljs:194
|
||||
msgid "dashboard.fonts.fonts-added"
|
||||
msgid_plural "dashboard.fonts.fonts-added"
|
||||
|
@ -695,6 +752,22 @@ msgstr "+ New project"
|
|||
msgid "dashboard.new-project-prefix"
|
||||
msgstr "New Project"
|
||||
|
||||
#: src/app/main/data/dashboard.cljs:72
|
||||
msgid "dashboard.permissions-change.viewer"
|
||||
msgstr "You are now a viewer on this team."
|
||||
|
||||
#: src/app/main/data/dashboard.cljs:75
|
||||
msgid "dashboard.permissions-change.editor"
|
||||
msgstr "You are now an editor on this team."
|
||||
|
||||
#: src/app/main/data/dashboard.cljs:78
|
||||
msgid "dashboard.permissions-change.admin"
|
||||
msgstr "You are now an admin on this team."
|
||||
|
||||
#: src/app/main/data/dashboard.cljs:81
|
||||
msgid "dashboard.permissions-change.owner"
|
||||
msgstr "You are now owner on this team."
|
||||
|
||||
#: src/app/main/ui/dashboard/search.cljs:60
|
||||
msgid "dashboard.no-matches-for"
|
||||
msgstr "No matches found for “%s“"
|
||||
|
|
|
@ -442,6 +442,58 @@ msgstr ""
|
|||
"con alguna plantilla ve a [Bibliotecas y "
|
||||
"plantillas](https://penpot.app/libraries-templates)."
|
||||
|
||||
#: src/app/main/ui/dashboard/placeholder.cljs:33
|
||||
msgid "dashboard.empty-placeholder-drafts-viewer-role"
|
||||
msgstr ""
|
||||
"Los archivos agregados a las bibliotecas aparecerán aquí."
|
||||
|
||||
|
||||
|
||||
|
||||
#: src/app/main/ui/dashboard/placeholder.cljs
|
||||
msgid "dashboard.empty-placeholder-libraries-title"
|
||||
msgstr "Aún no existen librerías compartidas."
|
||||
|
||||
#: src/app/main/ui/dashboard/placeholder.cljs
|
||||
#, markdown
|
||||
msgid "dashboard.empty-placeholder-libraries-subtitle"
|
||||
msgstr ""
|
||||
"Los archivos agregados a las bibliotecas aparecerán aquí. Si quieres probar "
|
||||
"con alguna plantilla ve a [Bibliotecas y "
|
||||
"plantillas](https://penpot.app/libraries-templates)."
|
||||
|
||||
#: src/app/main/ui/dashboard/placeholder.cljs
|
||||
msgid "dashboard.empty-placeholder-libraries-subtitle-viewer-role"
|
||||
msgstr "Los archivos agregados a las bibliotecas aparecerán aquí."
|
||||
|
||||
|
||||
#: src/app/main/ui/dashboard/files.cljs
|
||||
msgid "dashboard.empty-placeholder-drafts-title"
|
||||
msgstr "Aún no hay borradores."
|
||||
|
||||
#: src/app/main/ui/dashboard/files.cljs
|
||||
msgid "dashboard.empty-placeholder-drafts-subtitle"
|
||||
msgstr "Cuando un miembro del equipo cree algún borrador, este aparecerá aquí."
|
||||
|
||||
#: src/app/main/ui/dashboard/files.cljs
|
||||
msgid "dashboard.empty-placeholder-files-title"
|
||||
msgstr "Aún no hay archivos."
|
||||
|
||||
#: src/app/main/ui/dashboard/files.cljs
|
||||
msgid "dashboard.empty-placeholder-files-subtitle"
|
||||
msgstr "Cuando un miembro del equipo cree algún archivo, este aparecerá aquí."
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#: src/app/main/ui/dashboard/file_menu.cljs:249
|
||||
msgid "dashboard.export-binary-multi"
|
||||
msgstr "Descargar %s archivos Penpot (.penpot)"
|
||||
|
@ -544,6 +596,14 @@ msgstr "Ignorar todas"
|
|||
msgid "dashboard.fonts.empty-placeholder"
|
||||
msgstr "Las fuentes personalizadas que subas aparecerán aquí."
|
||||
|
||||
#: src/app/main/ui/dashboard/fonts.cljs:436
|
||||
msgid "dashboard.fonts.empty-placeholder-viewer"
|
||||
msgstr "Aún no hay fuentes personalizadas."
|
||||
|
||||
#: src/app/main/ui/dashboard/fonts.cljs:436
|
||||
msgid "dashboard.fonts.empty-placeholder-viewer-sub"
|
||||
msgstr "Cuando un miembro del equipo suba una fuente personalizada, esta aparecerá aquí."
|
||||
|
||||
#: src/app/main/ui/dashboard/fonts.cljs:194
|
||||
msgid "dashboard.fonts.fonts-added"
|
||||
msgid_plural "dashboard.fonts.fonts-added"
|
||||
|
@ -702,6 +762,22 @@ msgstr "+ Nuevo proyecto"
|
|||
msgid "dashboard.new-project-prefix"
|
||||
msgstr "Nuevo Proyecto"
|
||||
|
||||
#: src/app/main/data/dashboard.cljs:72
|
||||
msgid "dashboard.permissions-change.viewer"
|
||||
msgstr "Ahora eres lector del equipo."
|
||||
|
||||
#: src/app/main/data/dashboard.cljs:75
|
||||
msgid "dashboard.permissions-change.editor"
|
||||
msgstr "Ahora eres editor del equipo."
|
||||
|
||||
#: src/app/main/data/dashboard.cljs:78
|
||||
msgid "dashboard.permissions-change.admin"
|
||||
msgstr "Ahora eres administrador del equipo."
|
||||
|
||||
#: src/app/main/data/dashboard.cljs:81
|
||||
msgid "dashboard.permissions-change.owner"
|
||||
msgstr "Ahora eres el dueño del equipo."
|
||||
|
||||
#: src/app/main/ui/dashboard/search.cljs:60
|
||||
msgid "dashboard.no-matches-for"
|
||||
msgstr "No se encuentra “%s“"
|
||||
|
@ -2025,7 +2101,7 @@ msgstr "Solo lectura"
|
|||
|
||||
#: src/app/main/ui/dashboard/team.cljs:128, src/app/main/ui/dashboard/team.cljs:301, src/app/main/ui/dashboard/team.cljs:540
|
||||
msgid "labels.viewer"
|
||||
msgstr "Visualizador"
|
||||
msgstr "Lector"
|
||||
|
||||
#: src/app/main/ui/dashboard/sidebar.cljs:523, src/app/main/ui/dashboard/team.cljs:95, src/app/main/ui/dashboard/team.cljs:105, src/app/main/ui/dashboard/team.cljs:901
|
||||
msgid "labels.webhooks"
|
||||
|
|
Loading…
Add table
Reference in a new issue