mirror of
https://github.com/penpot/penpot.git
synced 2025-06-05 22:41:39 +02:00
Mainly removes an inconsistent use of path params and normalize all routes to use query params for make it extensible without breaking urls.
401 lines
13 KiB
Clojure
401 lines
13 KiB
Clojure
;; 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.data.common
|
|
"A general purpose events."
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.data.macros :as dm]
|
|
[app.common.schema :as sm]
|
|
[app.common.types.components-list :as ctkl]
|
|
[app.common.types.team :as ctt]
|
|
[app.main.data.modal :as modal]
|
|
[app.main.data.notifications :as ntf]
|
|
[app.main.data.persistence :as-alias dps]
|
|
[app.main.features :as features]
|
|
[app.main.repo :as rp]
|
|
[app.main.router :as rt]
|
|
[app.main.store :as st]
|
|
[app.util.dom :as-alias dom]
|
|
[app.util.i18n :refer [tr]]
|
|
[beicon.v2.core :as rx]
|
|
[potok.v2.core :as ptk]))
|
|
|
|
(declare go-to-dashboard-recent)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; SHARE LINK
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defn share-link-created
|
|
[link]
|
|
(ptk/reify ::share-link-created
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(update state :share-links (fnil conj []) link))))
|
|
|
|
(defn create-share-link
|
|
[params]
|
|
(ptk/reify ::create-share-link
|
|
ptk/WatchEvent
|
|
(watch [_ _ _]
|
|
(->> (rp/cmd! :create-share-link params)
|
|
(rx/map share-link-created)))))
|
|
|
|
(defn delete-share-link
|
|
[{:keys [id] :as link}]
|
|
(ptk/reify ::delete-share-link
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(update state :share-links
|
|
(fn [links]
|
|
(filterv #(not= id (:id %)) links))))
|
|
|
|
ptk/WatchEvent
|
|
(watch [_ _ _]
|
|
(->> (rp/cmd! :delete-share-link {:id id})
|
|
(rx/ignore)))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; NOTIFICATIONS
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defn force-reload!
|
|
[]
|
|
(.reload js/location))
|
|
|
|
(defn hide-notifications!
|
|
[]
|
|
(st/emit! (ntf/hide)))
|
|
|
|
(defn handle-notification
|
|
[{:keys [message code level] :as params}]
|
|
(ptk/reify ::show-notification
|
|
ptk/WatchEvent
|
|
(watch [_ _ _]
|
|
(case code
|
|
:upgrade-version
|
|
(rx/of (ntf/dialog
|
|
:content (tr "notifications.by-code.upgrade-version")
|
|
:controls :inline-actions
|
|
:type :inline
|
|
:level level
|
|
:actions [{:label "Refresh" :callback force-reload!}]
|
|
:tag :notification))
|
|
|
|
:maintenance
|
|
(rx/of (ntf/dialog
|
|
:content (tr "notifications.by-code.maintenance")
|
|
:controls :inline-actions
|
|
:type level
|
|
:actions [{:label (tr "labels.accept")
|
|
:callback hide-notifications!}]
|
|
:tag :notification))
|
|
|
|
(rx/of (ntf/dialog
|
|
:content message
|
|
:controls :close
|
|
:type level
|
|
:tag :notification))))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; SHARED LIBRARY
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defn show-shared-dialog
|
|
[file-id add-shared]
|
|
(ptk/reify ::show-shared-dialog
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [features (features/get-team-enabled-features state)
|
|
data (:workspace-data state)
|
|
file (:workspace-file state)]
|
|
(->> (if (and data file)
|
|
(rx/of {:name (:name file)
|
|
:components-count (count (ctkl/components-seq data))
|
|
:graphics-count (count (:media data))
|
|
:colors-count (count (:colors data))
|
|
:typography-count (count (:typographies data))})
|
|
(rp/cmd! :get-file-summary {:id file-id :features features}))
|
|
(rx/map (fn [summary]
|
|
(let [count (+ (:components-count summary)
|
|
(:graphics-count summary)
|
|
(:colors-count summary)
|
|
(:typography-count summary))]
|
|
(modal/show
|
|
{:type :confirm
|
|
:title (tr "modals.add-shared-confirm.message" (:name summary))
|
|
:message (if (zero? count) (tr "modals.add-shared-confirm-empty.hint") (tr "modals.add-shared-confirm.hint"))
|
|
:cancel-label (if (zero? count) (tr "labels.cancel") :omit)
|
|
:accept-label (tr "modals.add-shared-confirm.accept")
|
|
:accept-style :primary
|
|
:on-accept add-shared})))))))))
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Exportations
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(def ^:private schema:export-files
|
|
[:sequential {:title "Files"}
|
|
[:map {:title "FileParam"}
|
|
[:id ::sm/uuid]
|
|
[:name :string]
|
|
[:project-id ::sm/uuid]
|
|
[:is-shared ::sm/boolean]]])
|
|
|
|
(def check-export-files!
|
|
(sm/check-fn schema:export-files))
|
|
|
|
(def valid-export-formats
|
|
#{:binfile-v1 :binfile-v3 :legacy-zip})
|
|
|
|
(defn export-files
|
|
[files format]
|
|
(dm/assert!
|
|
"expected valid files param"
|
|
(check-export-files! files))
|
|
|
|
(dm/assert!
|
|
"expected valid format"
|
|
(contains? valid-export-formats format))
|
|
|
|
(ptk/reify ::export-files
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [features (features/get-team-enabled-features state)
|
|
team-id (:current-team-id state)]
|
|
(->> (rx/from files)
|
|
(rx/mapcat
|
|
(fn [file]
|
|
(->> (rp/cmd! :has-file-libraries {:file-id (:id file)})
|
|
(rx/map #(assoc file :has-libraries %)))))
|
|
(rx/reduce conj [])
|
|
(rx/map (fn [files]
|
|
(modal/show
|
|
{:type :export
|
|
:features features
|
|
:team-id team-id
|
|
:files files
|
|
:format format}))))))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Team Request
|
|
;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defn create-team-access-request
|
|
[params]
|
|
(ptk/reify ::create-team-access-request
|
|
ptk/WatchEvent
|
|
(watch [_ _ _]
|
|
(let [{:keys [on-success on-error]
|
|
:or {on-success identity
|
|
on-error rx/throw}} (meta params)]
|
|
(->> (rp/cmd! :create-team-access-request params)
|
|
(rx/tap on-success)
|
|
(rx/catch on-error))))))
|
|
|
|
(defn- get-change-role-msg
|
|
[role]
|
|
(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")))
|
|
|
|
(defn change-team-role
|
|
[{:keys [team-id role]}]
|
|
(dm/assert! (uuid? team-id))
|
|
(dm/assert! (contains? ctt/valid-roles role))
|
|
|
|
(ptk/reify ::change-team-role
|
|
ptk/WatchEvent
|
|
(watch [_ _ _]
|
|
(rx/of (ntf/info (get-change-role-msg role))))
|
|
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
(let [permissions (get ctt/permissions-for-role role)]
|
|
(-> state
|
|
(update :permissions merge permissions)
|
|
(update-in [:team :permissions] merge permissions)
|
|
(d/update-in-when [:teams team-id :permissions] merge permissions))))))
|
|
|
|
(defn team-membership-change
|
|
[{:keys [team-id team-name change]}]
|
|
(dm/assert! (uuid? team-id))
|
|
(ptk/reify ::team-membership-change
|
|
ptk/UpdateEvent
|
|
(update [_ state]
|
|
;; FIXME: Remove on 2.5
|
|
(assoc state :current-team-id (dm/get-in state [:profile :default-team-id])))
|
|
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(when (= :removed change)
|
|
(let [message (tr "dashboard.removed-from-team" team-name)
|
|
team-id (-> state :profile :default-team-id)]
|
|
(rx/concat
|
|
(rx/of (go-to-dashboard-recent :team-id team-id))
|
|
(->> (rx/of (ntf/info message))
|
|
;; Delay so the navigation can finish
|
|
(rx/delay 250))))))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; NAVEGATION EVENTS
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defn go-to-feedback
|
|
[]
|
|
(ptk/reify ::go-to-feedback
|
|
ptk/WatchEvent
|
|
(watch [_ _ _]
|
|
(rx/of (rt/nav :settings-feedback {}
|
|
::rt/new-window true
|
|
::rt/window-name "penpot-feedback")))))
|
|
|
|
(defn go-to-dashboard-files
|
|
[& {:keys [project-id team-id] :as options}]
|
|
(ptk/reify ::go-to-dashboard-files
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [profile (get state :profile)
|
|
team-id (or team-id (:current-team-id state))
|
|
project-id (if (= project-id :default)
|
|
(:default-project-id profile)
|
|
project-id)
|
|
|
|
params {:team-id team-id
|
|
:project-id project-id}]
|
|
(rx/of (rt/nav :dashboard-files params options))))))
|
|
|
|
(defn go-to-dashboard-search
|
|
[& {:keys [term] :as options}]
|
|
(ptk/reify ::go-to-dashboard-search
|
|
ptk/WatchEvent
|
|
(watch [_ state stream]
|
|
(let [team-id (:current-team-id state)]
|
|
(rx/merge
|
|
(->> (rx/of (rt/nav :dashboard-search
|
|
{:team-id team-id
|
|
:search-term term})
|
|
(modal/hide))
|
|
(rx/observe-on :async))
|
|
|
|
(->> stream
|
|
(rx/filter (ptk/type? ::rt/navigated))
|
|
(rx/take 1)
|
|
(rx/map (fn [_]
|
|
(ptk/event ::dom/focus-element
|
|
{:name "search-input"})))))))))
|
|
|
|
(defn go-to-dashboard-libraries
|
|
[& {:keys [team-id] :as options}]
|
|
(ptk/reify ::go-to-dashboard-libraries
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [team-id (or team-id (:current-team-id state))]
|
|
(rx/of (rt/nav :dashboard-libraries {:team-id team-id}))))))
|
|
|
|
|
|
(defn go-to-dashboard-fonts
|
|
[& {:keys [team-id] :as options}]
|
|
(ptk/reify ::go-to-dashboard-fonts
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [team-id (or team-id (:current-team-id state))]
|
|
(rx/of (rt/nav :dashboard-libraries {:team-id team-id}))))))
|
|
|
|
(defn go-to-dashboard-recent
|
|
[& {:keys [team-id] :as options}]
|
|
(ptk/reify ::go-to-dashboard-recent
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [profile (get state :profile)
|
|
team-id (cond
|
|
(= :default team-id)
|
|
(:default-team-id profile)
|
|
|
|
(uuid? team-id)
|
|
team-id
|
|
|
|
:else
|
|
(:current-team-id state))
|
|
params {:team-id team-id}]
|
|
(rx/of (modal/hide)
|
|
(rt/nav :dashboard-recent params options))))))
|
|
|
|
(defn go-to-dashboard-members
|
|
[& {:as options}]
|
|
(ptk/reify ::go-to-dashboard-members
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [team-id (:current-team-id state)]
|
|
(rx/of (rt/nav :dashboard-members {:team-id team-id}))))))
|
|
|
|
(defn go-to-dashboard-invitations
|
|
[& {:as options}]
|
|
(ptk/reify ::go-to-dashboard-invitations
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [team-id (:current-team-id state)]
|
|
(rx/of (rt/nav :dashboard-invitations {:team-id team-id}))))))
|
|
|
|
(defn go-to-dashboard-webhooks
|
|
[& {:as options}]
|
|
(ptk/reify ::go-to-dashboard-webhooks
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [team-id (:current-team-id state)]
|
|
(rx/of (rt/nav :dashboard-webhooks {:team-id team-id}))))))
|
|
|
|
(defn go-to-dashboard-settings
|
|
[& {:as options}]
|
|
(ptk/reify ::go-to-dashboard-settings
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [team-id (:current-team-id state)]
|
|
(rx/of (rt/nav :dashboard-settings {:team-id team-id}))))))
|
|
|
|
(defn go-to-workspace
|
|
[& {:keys [team-id file-id page-id layout] :as options}]
|
|
(ptk/reify ::go-to-workspace
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [team-id (or team-id (:current-team-id state))
|
|
file-id (or file-id (:current-file-id state))
|
|
;: FIXME: why not :current-page-id
|
|
page-id (or page-id
|
|
(dm/get-in state [:workspace-data :pages 0]))
|
|
params (-> (rt/get-params state)
|
|
(assoc :team-id team-id)
|
|
(assoc :file-id file-id)
|
|
(assoc :page-id page-id)
|
|
(assoc :layout layout)
|
|
(d/without-nils))]
|
|
(rx/of (rt/nav :workspace params options))))))
|
|
|
|
(defn go-to-viewer
|
|
[& {:keys [file-id page-id section frame-id index] :as options}]
|
|
(ptk/reify ::go-to-viewer
|
|
ptk/WatchEvent
|
|
(watch [_ state _]
|
|
(let [page-id (or page-id (:current-page-id state))
|
|
file-id (or file-id (:current-file-id state))
|
|
section (or section :interactions)
|
|
params {:file-id file-id
|
|
:page-id page-id
|
|
:section section
|
|
:frame-id frame-id
|
|
:index index}
|
|
params (d/without-nils params)
|
|
name (dm/str "viewer-" file-id)
|
|
options (merge {::rt/new-window true
|
|
::rt/window-name name}
|
|
options)]
|
|
(rx/of ::dps/force-persist
|
|
(rt/nav :viewer params options))))))
|
|
|