mirror of
https://github.com/penpot/penpot.git
synced 2025-01-21 06:02:32 -05:00
Merge pull request #5411 from penpot/niwinz-routing-refactor
♻️ Refactor application routing
This commit is contained in:
commit
0828d2e092
145 changed files with 3338 additions and 3061 deletions
|
@ -575,7 +575,7 @@
|
|||
(if-let [media-id (:media-id row)]
|
||||
(-> row
|
||||
(dissoc :media-id)
|
||||
(assoc :thumbnail-uri (resolve-public-uri media-id)))
|
||||
(assoc :thumbnail-id media-id))
|
||||
(dissoc row :media-id))))
|
||||
(map #(assoc % :library-summary (get-library-summary cfg %)))
|
||||
(map #(dissoc % :data))))))
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
[app.common.schema :as sm]
|
||||
[app.db :as db]
|
||||
[app.rpc :as-alias rpc]
|
||||
[app.rpc.commands.files :refer [resolve-public-uri]]
|
||||
[app.rpc.doc :as-alias doc]
|
||||
[app.util.services :as sv]))
|
||||
|
||||
|
@ -61,7 +60,7 @@
|
|||
(if-let [media-id (:media-id row)]
|
||||
(-> row
|
||||
(dissoc :media-id)
|
||||
(assoc :thumbnail-uri (resolve-public-uri media-id)))
|
||||
(assoc :thumbnail-id media-id))
|
||||
(dissoc row :media-id))))))
|
||||
|
||||
(def ^:private schema:search-files
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
:share-links links
|
||||
:libraries libs
|
||||
:file file
|
||||
:team team
|
||||
:team (assoc team :permissions perms)
|
||||
:permissions perms}))
|
||||
|
||||
(def schema:get-view-only-bundle
|
||||
|
|
|
@ -1010,6 +1010,9 @@
|
|||
(def valid-safe-number?
|
||||
(lazy-validator ::safe-number))
|
||||
|
||||
(def valid-text?
|
||||
(validator ::text))
|
||||
|
||||
(def check-safe-int!
|
||||
(check-fn ::safe-int))
|
||||
|
||||
|
|
|
@ -412,7 +412,6 @@
|
|||
(recur (when continue? (rest styles)) taking? to result))
|
||||
result))))
|
||||
|
||||
|
||||
(defn content->text
|
||||
"Given a root node of a text content extracts the texts with its associated styles"
|
||||
[content]
|
||||
|
|
|
@ -18,11 +18,18 @@
|
|||
java.nio.ByteBuffer)))
|
||||
|
||||
(defn uuid
|
||||
"Parse string uuid representation into proper UUID instance."
|
||||
"Creates an UUID instance from string, expectes valid uuid strings,
|
||||
the existense of validation is implementation detail"
|
||||
[s]
|
||||
#?(:clj (UUID/fromString s)
|
||||
:cljs (c/uuid s)))
|
||||
|
||||
(defn parse
|
||||
"Parse string uuid representation into proper UUID instance, validates input"
|
||||
[s]
|
||||
#?(:clj (UUID/fromString s)
|
||||
:cljs (c/parse-uuid s)))
|
||||
|
||||
(defn next
|
||||
[]
|
||||
#?(:clj (UUIDv8/create)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"~:id": "~ue5a24d1b-ef1e-812f-8004-52bab84be6f7",
|
||||
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6",
|
||||
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:created-at": "~m1715266551088",
|
||||
"~:modified-at": "~m1715266551088",
|
||||
"~:is-default": false,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"~:modified-at": "~m1714045654874",
|
||||
"~:name": "New File 2",
|
||||
"~:revn": 1,
|
||||
"~:thumbnail-id": "~u95d6fdd8-48d8-8148-8004-38af910d2dbe",
|
||||
"~:is-shared": false
|
||||
},
|
||||
{
|
||||
|
@ -15,6 +16,7 @@
|
|||
"~:modified-at": "~m1713519762931",
|
||||
"~:name": "New File 1",
|
||||
"~:revn": 1,
|
||||
"~:thumbnail-id": "~u95d6fdd8-48d8-8148-8004-38af910d2dbe",
|
||||
"~:is-shared": false
|
||||
}
|
||||
]
|
||||
|
|
BIN
frontend/playwright/data/dashboard/thumbnail.png
Normal file
BIN
frontend/playwright/data/dashboard/thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
23
frontend/playwright/data/get-teams-role-viewer.json
Normal file
23
frontend/playwright/data/get-teams-role-viewer.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
[{
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"layout/grid",
|
||||
"styles/v2",
|
||||
"fdata/pointer-map",
|
||||
"fdata/objects-map",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type"
|
||||
]
|
||||
},
|
||||
"~:permissions": {
|
||||
"~:type": "~:membership",
|
||||
"~:is-owner": false,
|
||||
"~:is-admin": false,
|
||||
"~:can-edit": false
|
||||
},
|
||||
"~:name": "Default",
|
||||
"~:modified-at": "~m1713533116375",
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f7920a",
|
||||
"~:created-at": "~m1713533116375",
|
||||
"~:is-default": true
|
||||
}]
|
23
frontend/playwright/data/get-teams.json
Normal file
23
frontend/playwright/data/get-teams.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
[{
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"layout/grid",
|
||||
"styles/v2",
|
||||
"fdata/pointer-map",
|
||||
"fdata/objects-map",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type"
|
||||
]
|
||||
},
|
||||
"~:permissions": {
|
||||
"~:type": "~:membership",
|
||||
"~:is-owner": true,
|
||||
"~:is-admin": true,
|
||||
"~:can-edit": true
|
||||
},
|
||||
"~:name": "Default",
|
||||
"~:modified-at": "~m1713533116375",
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f7920a",
|
||||
"~:created-at": "~m1713533116375",
|
||||
"~:is-default": true
|
||||
}]
|
|
@ -110,6 +110,10 @@ export class DashboardPage extends BaseWebSocketPage {
|
|||
"get-project-files?project-id=*",
|
||||
"dashboard/get-project-files.json",
|
||||
);
|
||||
|
||||
await this.mockRPC(/assets\/by-id/gi, "dashboard/thumbnail.png", {
|
||||
contentType: "image/png",
|
||||
});
|
||||
}
|
||||
|
||||
async setupNewProject() {
|
||||
|
@ -207,60 +211,64 @@ export class DashboardPage extends BaseWebSocketPage {
|
|||
|
||||
async goToDashboard() {
|
||||
await this.page.goto(
|
||||
`#/dashboard/team/${DashboardPage.anyTeamId}/projects`,
|
||||
`#/dashboard/recent?team-id=${DashboardPage.anyTeamId}`,
|
||||
);
|
||||
await expect(this.mainHeading).toBeVisible();
|
||||
}
|
||||
|
||||
async goToSecondTeamDashboard() {
|
||||
await this.page.goto(
|
||||
`#/dashboard/team/${DashboardPage.secondTeamId}/projects`,
|
||||
`#/dashboard/recent?team-id=${DashboardPage.secondTeamId}`,
|
||||
);
|
||||
}
|
||||
|
||||
async goToSecondTeamMembersSection() {
|
||||
await this.page.goto(
|
||||
`#/dashboard/team/${DashboardPage.secondTeamId}/members`,
|
||||
`#/dashboard/members?team-id=${DashboardPage.secondTeamId}`,
|
||||
);
|
||||
}
|
||||
|
||||
async goToSecondTeamInvitationsSection() {
|
||||
await this.page.goto(
|
||||
`#/dashboard/team/${DashboardPage.secondTeamId}/invitations`,
|
||||
`#/dashboard/invitations?team-id=${DashboardPage.secondTeamId}`,
|
||||
);
|
||||
}
|
||||
|
||||
async goToSecondTeamWebhooksSection() {
|
||||
await this.page.goto(
|
||||
`#/dashboard/team/${DashboardPage.secondTeamId}/webhooks`,
|
||||
`#/dashboard/webhooks?team-id=${DashboardPage.secondTeamId}`,
|
||||
);
|
||||
}
|
||||
|
||||
async goToSecondTeamWebhooksSection() {
|
||||
await this.page.goto(
|
||||
`#/dashboard/team/${DashboardPage.secondTeamId}/webhooks`,
|
||||
`#/dashboard/webhooks?team-id=${DashboardPage.secondTeamId}`,
|
||||
);
|
||||
}
|
||||
|
||||
async goToSecondTeamSettingsSection() {
|
||||
await this.page.goto(
|
||||
`#/dashboard/team/${DashboardPage.secondTeamId}/settings`,
|
||||
`#/dashboard/settings?team-id=${DashboardPage.secondTeamId}`,
|
||||
);
|
||||
}
|
||||
|
||||
async goToSearch() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.anyTeamId}/search`);
|
||||
await this.page.goto(
|
||||
`#/dashboard/search?team-id=${DashboardPage.anyTeamId}`,
|
||||
);
|
||||
}
|
||||
|
||||
async goToDrafts() {
|
||||
await this.page.goto(
|
||||
`#/dashboard/team/${DashboardPage.anyTeamId}/projects/${DashboardPage.draftProjectId}`,
|
||||
`#/dashboard/files?team-id=${DashboardPage.anyTeamId}&project-id=${DashboardPage.draftProjectId}`,
|
||||
);
|
||||
await expect(this.mainHeading).toHaveText("Drafts");
|
||||
}
|
||||
|
||||
async goToFonts() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.anyTeamId}/fonts`);
|
||||
await this.page.goto(
|
||||
`#/dashboard/fonts?team-id=${DashboardPage.anyTeamId}`,
|
||||
);
|
||||
await expect(this.mainHeading).toHaveText("Fonts");
|
||||
}
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ export class ViewerPage extends BaseWebSocketPage {
|
|||
pageId = ViewerPage.anyPageId,
|
||||
} = {}) {
|
||||
await this.page.goto(
|
||||
`/#/view/${fileId}?page-id=${pageId}§ion=interactions&index=0`,
|
||||
`/#/view?file-id=${fileId}&page-id=${pageId}§ion=interactions&index=0`,
|
||||
);
|
||||
|
||||
this.#ws = await this.waitForNotificationsWebSocket();
|
||||
|
|
|
@ -36,6 +36,14 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||
"get-team?id=*",
|
||||
"workspace/get-team-default.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams.json");
|
||||
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-team-members?team-id=*",
|
||||
"logged-in-user/get-team-members-your-penpot.json",
|
||||
);
|
||||
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-profiles-for-file-comments?file-id=*",
|
||||
|
@ -43,6 +51,7 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||
);
|
||||
}
|
||||
|
||||
static anyTeamId = "c7ce0794-0992-8105-8004-38e630f7920a";
|
||||
static anyProjectId = "c7ce0794-0992-8105-8004-38e630f7920b";
|
||||
static anyFileId = "c7ce0794-0992-8105-8004-38f280443849";
|
||||
static anyPageId = "c7ce0794-0992-8105-8004-38f28044384a";
|
||||
|
@ -83,7 +92,7 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||
pageId = WorkspacePage.anyPageId,
|
||||
} = {}) {
|
||||
await this.page.goto(
|
||||
`/#/workspace/${WorkspacePage.anyProjectId}/${fileId}?page-id=${pageId}`,
|
||||
`/#/workspace?team-id=${WorkspacePage.anyTeamId}&file-id=${fileId}&page-id=${pageId}`,
|
||||
);
|
||||
|
||||
this.#ws = await this.waitForNotificationsWebSocket();
|
||||
|
|
|
@ -7,11 +7,7 @@ test.beforeEach(async ({ page }) => {
|
|||
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile(page);
|
||||
await WorkspacePage.mockRPC(
|
||||
page,
|
||||
"get-team?id=*",
|
||||
"workspace/get-team-role-viewer.json",
|
||||
);
|
||||
await WorkspacePage.mockRPC(page, "get-teams", "get-teams-role-viewer.json");
|
||||
|
||||
await workspacePage.goToWorkspace();
|
||||
});
|
||||
|
|
|
@ -10,8 +10,9 @@
|
|||
[app.common.logging :as log]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.auth :as da]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.profile :as dp]
|
||||
[app.main.data.websocket :as ws]
|
||||
[app.main.errors]
|
||||
[app.main.features :as feat]
|
||||
|
@ -66,14 +67,14 @@
|
|||
authenticated user; proceed to fetch teams."
|
||||
[stream]
|
||||
(rx/merge
|
||||
(rx/of (du/fetch-profile))
|
||||
(rx/of (dp/fetch-profile))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::profile-fetched))
|
||||
(rx/filter dp/profile-fetched?)
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/mapcat (fn [profile]
|
||||
(if (du/is-authenticated? profile)
|
||||
(rx/of (du/fetch-teams))
|
||||
(if (dp/is-authenticated? profile)
|
||||
(rx/of (dp/initialize-profile profile))
|
||||
(rx/empty))))
|
||||
(rx/observe-on :async))))
|
||||
|
||||
|
@ -92,19 +93,24 @@
|
|||
|
||||
(initialize-profile stream)
|
||||
|
||||
;; Watch for profile deletion events
|
||||
(->> stream
|
||||
(rx/filter dp/profile-deleted?)
|
||||
(rx/map da/logged-out))
|
||||
|
||||
;; Once profile is fetched, initialize all penpot application
|
||||
;; routes
|
||||
(->> stream
|
||||
(rx/filter du/profile-fetched?)
|
||||
(rx/filter dp/profile-fetched?)
|
||||
(rx/take 1)
|
||||
(rx/map #(rt/init-routes)))
|
||||
|
||||
;; Once profile fetched and the current user is authenticated,
|
||||
;; proceed to initialize the websockets connection.
|
||||
(->> stream
|
||||
(rx/filter du/profile-fetched?)
|
||||
(rx/filter dp/profile-fetched?)
|
||||
(rx/map deref)
|
||||
(rx/filter du/is-authenticated?)
|
||||
(rx/filter dp/is-authenticated?)
|
||||
(rx/take 1)
|
||||
(rx/map #(ws/initialize)))))))
|
||||
|
||||
|
|
319
frontend/src/app/main/data/auth.cljs
Normal file
319
frontend/src/app/main/data/auth.cljs
Normal file
|
@ -0,0 +1,319 @@
|
|||
;; 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.auth
|
||||
"Auth related data events"
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.profile :as dp]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.data.websocket :as ws]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.storage :as storage]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
;; --- HELPERS
|
||||
|
||||
(defn is-authenticated?
|
||||
[{:keys [id]}]
|
||||
(and (uuid? id) (not= id uuid/zero)))
|
||||
|
||||
;; --- EVENT: login
|
||||
|
||||
(defn- logged-in
|
||||
"This is the main event that is executed once we have logged in
|
||||
profile. The profile can proceed from standard login or from
|
||||
accepting invitation, or third party auth signup or singin."
|
||||
[{:keys [props] :as profile}]
|
||||
(letfn [(get-redirect-events [teams]
|
||||
(if-let [redirect-href (:login-redirect storage/session)]
|
||||
(binding [storage/*sync* true]
|
||||
(swap! storage/session dissoc :login-redirect)
|
||||
(if (= redirect-href (rt/get-current-href))
|
||||
(rx/of (rt/reload true))
|
||||
(rx/of (rt/nav-raw :href redirect-href))))
|
||||
(if-let [file-id (get props :welcome-file-id)]
|
||||
(rx/of (dcm/go-to-workspace
|
||||
:file-id file-id
|
||||
:team-id (:default-team-id profile))
|
||||
(dp/update-profile-props {:welcome-file-id nil}))
|
||||
|
||||
(let [teams (into #{} (map :id) teams)
|
||||
team-id (dtm/get-last-team-id)
|
||||
team-id (if (and team-id (contains? teams team-id))
|
||||
team-id
|
||||
(:default-team-id profile))]
|
||||
(rx/of (dcm/go-to-dashboard-recent {:team-id team-id}))))))]
|
||||
|
||||
(ptk/reify ::logged-in
|
||||
ev/Event
|
||||
(-data [_]
|
||||
{::ev/name "signin"
|
||||
::ev/type "identify"
|
||||
:email (:email profile)
|
||||
:auth-backend (:auth-backend profile)
|
||||
:fullname (:fullname profile)
|
||||
:is-muted (:is-muted profile)
|
||||
:default-team-id (:default-team-id profile)
|
||||
:default-project-id (:default-project-id profile)})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(->> (rx/merge
|
||||
(rx/of (dp/initialize-profile profile)
|
||||
(ws/initialize)
|
||||
(dtm/fetch-teams))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dtm/teams-fetched))
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/mapcat get-redirect-events)))
|
||||
|
||||
(rx/observe-on :async))))))
|
||||
|
||||
(declare login-from-register)
|
||||
|
||||
(defn login
|
||||
[{:keys [email password invitation-token] :as data}]
|
||||
(ptk/reify ::login
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error rx/throw
|
||||
on-success identity}} (meta data)
|
||||
|
||||
params {:email email
|
||||
:password password
|
||||
:invitation-token invitation-token}]
|
||||
|
||||
;; NOTE: We can't take the profile value from login because
|
||||
;; there are cases when login is successful but the cookie is
|
||||
;; not set properly (because of possible misconfiguration).
|
||||
;; So, we proceed to make an additional call to fetch the
|
||||
;; profile, and ensure that cookie is set correctly. If
|
||||
;; profile fetch is successful, we mark the user logged in, if
|
||||
;; the returned profile is an NOT authenticated profile, we
|
||||
;; proceed to logout and show an error message.
|
||||
|
||||
(->> (rp/cmd! :login-with-password (d/without-nils params))
|
||||
(rx/merge-map (fn [data]
|
||||
(rx/merge
|
||||
(rx/of (dp/fetch-profile))
|
||||
(->> stream
|
||||
(rx/filter dp/profile-fetched?)
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/filter (complement is-authenticated?))
|
||||
(rx/tap on-error)
|
||||
(rx/map #(ex/raise :type :authentication))
|
||||
(rx/observe-on :async))
|
||||
|
||||
(->> stream
|
||||
(rx/filter dp/profile-fetched?)
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/filter is-authenticated?)
|
||||
(rx/map (fn [profile]
|
||||
(with-meta (merge data profile)
|
||||
{::ev/source "login"})))
|
||||
(rx/tap on-success)
|
||||
(rx/map logged-in)
|
||||
(rx/observe-on :async)))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(def ^:private schema:login-with-ldap
|
||||
[:map {:title "login-with-ldap"}
|
||||
[:email ::sm/email]
|
||||
[:password :string]])
|
||||
|
||||
(defn login-with-ldap
|
||||
[params]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid params"
|
||||
(sm/check schema:login-with-ldap params))
|
||||
|
||||
(ptk/reify ::login-with-ldap
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error rx/throw
|
||||
on-success identity}} (meta params)]
|
||||
(->> (rp/cmd! :login-with-ldap params)
|
||||
(rx/tap on-success)
|
||||
(rx/map (fn [profile]
|
||||
(-> profile
|
||||
(with-meta {::ev/source "login-with-ldap"})
|
||||
(logged-in))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn login-from-token
|
||||
"Used mainly as flow continuation after token validation."
|
||||
[{:keys [profile] :as tdata}]
|
||||
(ptk/reify ::login-from-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rx/of (logged-in (with-meta profile {::ev/source "login-with-token"})))
|
||||
;; NOTE: we need this to be asynchronous because the effect
|
||||
;; should be called before proceed with the login process
|
||||
(rx/observe-on :async)))))
|
||||
|
||||
(defn login-from-register
|
||||
"Event used mainly for mark current session as logged-in in after the
|
||||
user successfully registered using third party auth provider (in this
|
||||
case we dont need to verify the email)."
|
||||
[]
|
||||
(ptk/reify ::login-from-register
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(rx/merge
|
||||
(rx/of (dp/fetch-profile))
|
||||
(->> stream
|
||||
(rx/filter dp/profile-fetched?)
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/filter is-authenticated?)
|
||||
(rx/map (fn [profile]
|
||||
(with-meta profile
|
||||
{::ev/source "register"})))
|
||||
(rx/map logged-in)
|
||||
(rx/observe-on :async))))))
|
||||
|
||||
;; --- EVENT: logout
|
||||
|
||||
(defn logged-out
|
||||
[]
|
||||
(ptk/reify ::logged-out
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(select-keys state [:route :router :session-id :history]))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/merge
|
||||
;; NOTE: We need the `effect` of the current event to be
|
||||
;; executed before the redirect.
|
||||
(->> (rx/of (rt/nav :auth-login))
|
||||
(rx/observe-on :async))
|
||||
(rx/of (ws/finalize))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
;; We prefer to keek some stuff in the storage like the current-team-id and the profile
|
||||
(swap! storage/user (constantly {})))))
|
||||
|
||||
(defn logout
|
||||
[]
|
||||
(ptk/reify ::logout
|
||||
ev/Event
|
||||
(-data [_] {})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [profile-id (:profile-id state)]
|
||||
(->> (rx/interval 500)
|
||||
(rx/take 1)
|
||||
(rx/mapcat (fn [_]
|
||||
(->> (rp/cmd! :logout {:profile-id profile-id})
|
||||
(rx/delay-at-least 300)
|
||||
(rx/catch (constantly (rx/of 1))))))
|
||||
(rx/map logged-out))))))
|
||||
|
||||
;; --- Update Profile
|
||||
|
||||
(def ^:private
|
||||
schema:request-profile-recovery
|
||||
[:map {:title "request-profile-recovery" :closed true}
|
||||
[:email ::sm/email]])
|
||||
|
||||
(defn request-profile-recovery
|
||||
[data]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid parameters"
|
||||
(sm/check schema:request-profile-recovery data))
|
||||
|
||||
(ptk/reify ::request-profile-recovery
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error rx/throw
|
||||
on-success identity}} (meta data)]
|
||||
|
||||
(->> (rp/cmd! :request-profile-recovery data)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: recover-profile (Password)
|
||||
|
||||
(def ^:private
|
||||
schema:recover-profile
|
||||
[:map {:title "recover-profile" :closed true}
|
||||
[:password :string]
|
||||
[:token :string]])
|
||||
|
||||
(defn recover-profile
|
||||
[data]
|
||||
(dm/assert!
|
||||
"expected valid arguments"
|
||||
(sm/check schema:recover-profile data))
|
||||
|
||||
(ptk/reify ::recover-profile
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error rx/throw
|
||||
on-success identity}} (meta data)]
|
||||
(->> (rp/cmd! :recover-profile data)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: crete-demo-profile
|
||||
|
||||
(defn create-demo-profile
|
||||
[]
|
||||
(ptk/reify ::create-demo-profile
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :create-demo-profile {})
|
||||
(rx/map login)))))
|
||||
|
||||
(defn show-redirect-error
|
||||
"A helper event that interprets the OIDC redirect errors on the URI
|
||||
and shows an appropriate error message using the notification
|
||||
banners."
|
||||
[error]
|
||||
(ptk/reify ::show-redirect-error
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(when-let [hint (case error
|
||||
"registration-disabled"
|
||||
(tr "errors.registration-disabled")
|
||||
"profile-blocked"
|
||||
(tr "errors.profile-blocked")
|
||||
"auth-provider-not-allowed"
|
||||
(tr "errors.auth-provider-not-allowed")
|
||||
"email-domain-not-allowed"
|
||||
(tr "errors.email-domain-not-allowed")
|
||||
|
||||
;; We explicitly do not show any error here, it a explicit user operation.
|
||||
"unable-to-auth"
|
||||
nil
|
||||
|
||||
(tr "errors.generic"))]
|
||||
|
||||
(rx/of (ntf/warn hint))))))
|
|
@ -12,7 +12,7 @@
|
|||
[app.common.schema :as sm]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.repo :as rp]
|
||||
[beicon.v2.core :as rx]
|
||||
|
@ -603,3 +603,18 @@
|
|||
(filter (fn [comment] (some #(= % (:frame-id comment)) frame-ids?)))
|
||||
(map update-comment-thread-frame)
|
||||
(rx/from))))))
|
||||
|
||||
(defn fetch-profiles
|
||||
"Fetch or refresh all profile data for comments of the current file"
|
||||
[]
|
||||
(ptk/reify ::fetch-comments-profiles
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)
|
||||
share-id (or (-> state :viewer-local :share-id)
|
||||
(:current-share-id state))]
|
||||
(->> (rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id})
|
||||
(rx/map (fn [profiles]
|
||||
#(update % :profiles merge (d/index-by :id profiles)))))))))
|
||||
|
||||
|
||||
|
|
|
@ -14,14 +14,18 @@
|
|||
[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]]
|
||||
[app.util.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(declare go-to-dashboard-recent)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SHARE LINK
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -233,12 +237,165 @@
|
|||
(watch [_ state _]
|
||||
(when (= :removed change)
|
||||
(let [message (tr "dashboard.removed-from-team" team-name)
|
||||
profile (:profile state)]
|
||||
team-id (-> state :profile :default-team-id)]
|
||||
(rx/concat
|
||||
(rx/of (rt/nav :dashboard-projects {:team-id (:default-team-id profile)}))
|
||||
(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))))))
|
||||
|
||||
|
|
|
@ -12,26 +12,17 @@
|
|||
[app.common.files.helpers :as cfh]
|
||||
[app.common.logging :as log]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.team :as ctt]
|
||||
[app.common.uri :as u]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.common :as dc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.data.media :as di]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.websocket :as dws]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.sse :as sse]
|
||||
[app.util.time :as dt]
|
||||
[app.util.timers :as tm]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.set :as set]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
@ -43,143 +34,38 @@
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(declare fetch-projects)
|
||||
(declare fetch-team-members)
|
||||
(declare process-message)
|
||||
|
||||
(defn initialize
|
||||
[{:keys [id]}]
|
||||
(dm/assert! (uuid? id))
|
||||
[]
|
||||
(ptk/reify ::initialize
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [prev-team-id (:current-team-id state)]
|
||||
(cond-> state
|
||||
(not= prev-team-id id)
|
||||
(-> (dissoc :current-team-initialized)
|
||||
(dissoc :dashboard-files)
|
||||
(dissoc :dashboard-projects)
|
||||
(dissoc :dashboard-shared-files)
|
||||
(dissoc :dashboard-recent-files)
|
||||
(dissoc :dashboard-team-members)
|
||||
(dissoc :dashboard-team-stats)
|
||||
(assoc :current-team-id id)
|
||||
(update :workspace-global dissoc :default-font)))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stopper (rx/filter (ptk/type? ::finalize) stream)
|
||||
(let [stopper (rx/filter (ptk/type? ::finalize) stream)
|
||||
profile-id (:profile-id state)]
|
||||
|
||||
(->> (rx/merge
|
||||
;; fetch teams must be first in case the team doesn't exist
|
||||
(ptk/watch (du/fetch-teams) state stream)
|
||||
(ptk/watch (df/load-team-fonts id) state stream)
|
||||
(ptk/watch (fetch-projects) state stream)
|
||||
(ptk/watch (fetch-team-members) state stream)
|
||||
(ptk/watch (du/fetch-users) state stream)
|
||||
|
||||
(rx/of (fetch-projects)
|
||||
(df/fetch-fonts))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dws/message))
|
||||
(rx/map deref)
|
||||
(rx/filter (fn [{:keys [topic] :as msg}]
|
||||
(or (= topic uuid/zero)
|
||||
(= topic profile-id))))
|
||||
(rx/map process-message))
|
||||
|
||||
;; Once the teams are fecthed, initialize features related
|
||||
;; to currently active team
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::du/teams-fetched))
|
||||
(rx/observe-on :async)
|
||||
(rx/mapcat deref)
|
||||
(rx/filter #(= id (:id %)))
|
||||
(rx/mapcat (fn [team]
|
||||
(rx/of (du/set-current-team team)
|
||||
#(assoc % :current-team-initialized true))))))
|
||||
(rx/map process-message)
|
||||
(rx/ignore)))
|
||||
|
||||
(rx/take-until stopper))))))
|
||||
|
||||
(defn finalize
|
||||
[params]
|
||||
(ptk/data-event ::finalize params))
|
||||
[]
|
||||
(ptk/data-event ::finalize {}))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Fetching (context aware: current team)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; --- EVENT: fetch-team-members
|
||||
|
||||
(defn team-members-fetched
|
||||
[members]
|
||||
(ptk/reify ::team-members-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :dashboard-team-members (d/index-by :id members)))))
|
||||
|
||||
(defn fetch-team-members
|
||||
([] (fetch-team-members nil))
|
||||
([team-id]
|
||||
(ptk/reify ::fetch-team-members
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (or team-id (:current-team-id state))]
|
||||
(assert (uuid? team-id) "expected team-id to be resolved")
|
||||
(->> (rp/cmd! :get-team-members {:team-id team-id})
|
||||
(rx/map team-members-fetched)))))))
|
||||
|
||||
;; --- EVENT: fetch-team-stats
|
||||
|
||||
(defn team-stats-fetched
|
||||
[stats]
|
||||
(ptk/reify ::team-stats-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :dashboard-team-stats stats))))
|
||||
|
||||
(defn fetch-team-stats
|
||||
[team-id]
|
||||
(ptk/reify ::fetch-team-stats
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :get-team-stats {:team-id team-id})
|
||||
(rx/map team-stats-fetched)))))
|
||||
|
||||
;; --- EVENT: fetch-team-invitations
|
||||
|
||||
(defn team-invitations-fetched
|
||||
[invitations]
|
||||
(ptk/reify ::team-invitations-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :dashboard-team-invitations invitations))))
|
||||
|
||||
(defn fetch-team-invitations
|
||||
[]
|
||||
(ptk/reify ::fetch-team-invitations
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/cmd! :get-team-invitations {:team-id team-id})
|
||||
(rx/map team-invitations-fetched))))))
|
||||
|
||||
;; --- EVENT: fetch-team-webhooks
|
||||
|
||||
(defn team-webhooks-fetched
|
||||
[webhooks]
|
||||
(ptk/reify ::team-webhooks-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :dashboard-team-webhooks webhooks))))
|
||||
|
||||
(defn fetch-team-webhooks
|
||||
[]
|
||||
(ptk/reify ::fetch-team-webhooks
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/cmd! :get-webhooks {:team-id team-id})
|
||||
(rx/map team-webhooks-fetched))))))
|
||||
|
||||
;; --- EVENT: fetch-projects
|
||||
|
||||
(defn projects-fetched
|
||||
|
@ -187,8 +73,10 @@
|
|||
(ptk/reify ::projects-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [projects (d/index-by :id projects)]
|
||||
(assoc state :dashboard-projects projects)))))
|
||||
(reduce (fn [state {:keys [id] :as project}]
|
||||
(update-in state [:projects id] merge project))
|
||||
state
|
||||
projects))))
|
||||
|
||||
(defn fetch-projects
|
||||
[]
|
||||
|
@ -201,82 +89,28 @@
|
|||
|
||||
;; --- EVENT: search
|
||||
|
||||
(defn search-result-fetched
|
||||
[result]
|
||||
(ptk/reify ::search-result-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :dashboard-search-result result))))
|
||||
|
||||
(def schema:search-params
|
||||
(def ^:private schema:search-params
|
||||
[:map {:closed true}
|
||||
[:search-term [:maybe :string]]])
|
||||
|
||||
(def ^:private check-search-params
|
||||
(sm/check-fn schema:search-params))
|
||||
|
||||
(defn search
|
||||
[params]
|
||||
(dm/assert! schema:search-params params)
|
||||
(ptk/reify ::search
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(dissoc state :dashboard-search-result))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/cmd! :search-files params)
|
||||
(rx/map search-result-fetched))))))
|
||||
|
||||
;; --- EVENT: files
|
||||
|
||||
(defn files-fetched
|
||||
[project-id files]
|
||||
(letfn [(remove-project-files [files]
|
||||
(reduce-kv (fn [result id file]
|
||||
(cond-> result
|
||||
(= (:project-id file) project-id) (dissoc id)))
|
||||
files
|
||||
files))]
|
||||
(ptk/reify ::files-fetched
|
||||
(let [params (check-search-params params)]
|
||||
(ptk/reify ::search
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :dashboard-files
|
||||
(fn [state]
|
||||
(let [state (remove-project-files state)]
|
||||
(reduce #(assoc %1 (:id %2) %2) state files))))
|
||||
(assoc-in [:dashboard-projects project-id :count] (count files)))))))
|
||||
(dissoc state :search-result))
|
||||
|
||||
(defn fetch-files
|
||||
[{:keys [project-id] :as params}]
|
||||
(dm/assert! (uuid? project-id))
|
||||
(ptk/reify ::fetch-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :get-project-files {:project-id project-id})
|
||||
(rx/map #(files-fetched project-id %))))))
|
||||
|
||||
;; --- EVENT: shared-files
|
||||
|
||||
(defn shared-files-fetched
|
||||
[files]
|
||||
(ptk/reify ::shared-files-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [files (d/index-by :id files)]
|
||||
(-> state
|
||||
(assoc :dashboard-shared-files files)
|
||||
(update :dashboard-files d/merge files))))))
|
||||
|
||||
(defn fetch-shared-files
|
||||
([] (fetch-shared-files nil))
|
||||
([team-id]
|
||||
(ptk/reify ::fetch-shared-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (or team-id (:current-team-id state))]
|
||||
(->> (rp/cmd! :get-team-shared-files {:team-id team-id})
|
||||
(rx/map shared-files-fetched)))))))
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/cmd! :search-files params)
|
||||
(rx/map (fn [result]
|
||||
#(assoc % :search-result result)))))))))
|
||||
|
||||
;; --- EVENT: recent-files
|
||||
|
||||
|
@ -287,8 +121,8 @@
|
|||
(update [_ state]
|
||||
(let [files (d/index-by :id files)]
|
||||
(-> state
|
||||
(assoc :dashboard-recent-files files)
|
||||
(update :dashboard-files d/merge files))))))
|
||||
(assoc :recent-files files)
|
||||
(update :files d/merge files))))))
|
||||
|
||||
(defn fetch-recent-files
|
||||
[]
|
||||
|
@ -325,27 +159,22 @@
|
|||
(ptk/reify ::clear-file-select
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :dashboard-local
|
||||
assoc :selected-files #{}
|
||||
:selected-project nil
|
||||
:menu-open false
|
||||
:menu-pos nil))))
|
||||
(-> state
|
||||
(dissoc :selected-files)
|
||||
(dissoc :selected-project)
|
||||
(update :dashboard-local dissoc :menu-open :menu-pos)))))
|
||||
|
||||
(defn toggle-file-select
|
||||
[{:keys [id project-id] :as file}]
|
||||
(ptk/reify ::toggle-file-select
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [selected-project-id (get-in state [:dashboard-local :selected-project])]
|
||||
(let [selected-project-id (get state :selected-project)]
|
||||
(if (or (nil? selected-project-id)
|
||||
(= selected-project-id project-id))
|
||||
(update state :dashboard-local
|
||||
(fn [local]
|
||||
(-> local
|
||||
(update :selected-files #(if (contains? % id)
|
||||
(disj % id)
|
||||
(conj % id)))
|
||||
(assoc :selected-project project-id))))
|
||||
(-> state
|
||||
(update :selected-files #(if (contains? % id) (disj % id) (conj % id)))
|
||||
(assoc :selected-project project-id))
|
||||
state)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -399,320 +228,6 @@
|
|||
;; Data Modification
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; --- EVENT: create-team
|
||||
|
||||
(defn team-created
|
||||
[team]
|
||||
(ptk/reify ::team-created
|
||||
IDeref
|
||||
(-deref [_] team)))
|
||||
|
||||
(defn create-team
|
||||
[{:keys [name] :as params}]
|
||||
(dm/assert! (string? name))
|
||||
(ptk/reify ::create-team
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
features (features/get-enabled-features state)
|
||||
params {:name name :features features}]
|
||||
(->> (rp/cmd! :create-team (with-meta params (meta it)))
|
||||
(rx/tap on-success)
|
||||
(rx/map team-created)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: create-team-with-invitations
|
||||
|
||||
(defn create-team-with-invitations
|
||||
[{:keys [name emails role] :as params}]
|
||||
(ptk/reify ::create-team-with-invitations
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
features (features/get-enabled-features state)
|
||||
params {:name name
|
||||
:emails emails
|
||||
:role role
|
||||
:features features}]
|
||||
(->> (rp/cmd! :create-team-with-invitations (with-meta params (meta it)))
|
||||
(rx/tap on-success)
|
||||
(rx/map team-created)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: update-team
|
||||
|
||||
(defn update-team
|
||||
[{:keys [id name] :as params}]
|
||||
(ptk/reify ::update-team
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:teams id :name] name)
|
||||
(assoc-in [:team :name] name)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :update-team params)
|
||||
(rx/ignore)))))
|
||||
|
||||
(defn update-team-photo
|
||||
[file]
|
||||
(dm/assert!
|
||||
"expected a valid blob for `file` param"
|
||||
(di/blob? file))
|
||||
(ptk/reify ::update-team-photo
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [on-success di/notify-finished-loading
|
||||
on-error #(do (di/notify-finished-loading)
|
||||
(di/process-error %))
|
||||
team-id (:current-team-id state)
|
||||
prepare #(hash-map :file % :team-id team-id)]
|
||||
|
||||
(di/notify-start-loading)
|
||||
(->> (rx/of file)
|
||||
(rx/map di/validate-file)
|
||||
(rx/map prepare)
|
||||
(rx/mapcat #(rp/cmd! :update-team-photo %))
|
||||
(rx/tap on-success)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (du/fetch-teams)
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "update-team-photo"
|
||||
:team-id team-id}))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn update-team-member-role
|
||||
[{:keys [role member-id] :as params}]
|
||||
(dm/assert! (uuid? member-id))
|
||||
(dm/assert! (contains? ctt/valid-roles role))
|
||||
|
||||
(ptk/reify ::update-team-member-role
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/cmd! :update-team-member-role params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (fetch-team-members)
|
||||
(du/fetch-teams)
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "update-team-member-role"
|
||||
:team-id team-id
|
||||
:role role
|
||||
:member-id member-id})))))))))
|
||||
|
||||
(defn delete-team-member
|
||||
[{:keys [member-id] :as params}]
|
||||
(dm/assert! (uuid? member-id))
|
||||
(ptk/reify ::delete-team-member
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/cmd! :delete-team-member params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (fetch-team-members)
|
||||
(du/fetch-teams)
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "delete-team-member"
|
||||
:team-id team-id
|
||||
:member-id member-id})))))))))
|
||||
|
||||
(defn leave-team
|
||||
[{:keys [reassign-to] :as params}]
|
||||
(dm/assert! (or (nil? reassign-to)
|
||||
(uuid? reassign-to)))
|
||||
|
||||
(ptk/reify ::leave-team
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
team-id (:current-team-id state)
|
||||
params (cond-> {:id team-id}
|
||||
(uuid? reassign-to)
|
||||
(assoc :reassign-to reassign-to))]
|
||||
(->> (rp/cmd! :leave-team params)
|
||||
(rx/tap #(tm/schedule on-success))
|
||||
(rx/map (fn [_]
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "leave-team"
|
||||
:reassign-to reassign-to
|
||||
:team-id team-id})))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn invite-team-members
|
||||
[{:keys [emails role team-id resend?] :as params}]
|
||||
(dm/assert! (keyword? role))
|
||||
(dm/assert! (uuid? team-id))
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid set of emails"
|
||||
(sm/check-set-of-emails! emails))
|
||||
|
||||
(ptk/reify ::invite-team-members
|
||||
ev/Event
|
||||
(-data [_]
|
||||
{:role role
|
||||
:team-id team-id
|
||||
:resend resend?})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [it _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
params (dissoc params :resend?)]
|
||||
(->> (rp/cmd! :create-team-invitations (with-meta params (meta it)))
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
|
||||
(defn copy-invitation-link
|
||||
[{:keys [email team-id] :as params}]
|
||||
(dm/assert!
|
||||
"expected a valid email"
|
||||
(sm/check-email! email))
|
||||
|
||||
(dm/assert! (uuid? team-id))
|
||||
|
||||
(ptk/reify ::copy-invitation-link
|
||||
IDeref
|
||||
(-deref [_] {:email email :team-id team-id})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
router (:router state)]
|
||||
|
||||
(->> (rp/cmd! :get-team-invitation-token params)
|
||||
(rx/map (fn [params]
|
||||
(rt/resolve router :auth-verify-token {} params)))
|
||||
(rx/map (fn [fragment]
|
||||
(assoc cf/public-uri :fragment fragment)))
|
||||
(rx/tap (fn [uri]
|
||||
(wapi/write-to-clipboard (str uri))))
|
||||
(rx/tap on-success)
|
||||
(rx/ignore)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
|
||||
(defn update-team-invitation-role
|
||||
[{:keys [email team-id role] :as params}]
|
||||
(dm/assert!
|
||||
"expected a valid email"
|
||||
(sm/check-email! email))
|
||||
|
||||
(dm/assert! (uuid? team-id))
|
||||
(dm/assert! (contains? ctt/valid-roles role))
|
||||
|
||||
(ptk/reify ::update-team-invitation-role
|
||||
IDeref
|
||||
(-deref [_] {:role role})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :update-team-invitation-role params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn delete-team-invitation
|
||||
[{:keys [email team-id] :as params}]
|
||||
(dm/assert! (sm/check-email! email))
|
||||
(dm/assert! (uuid? team-id))
|
||||
(ptk/reify ::delete-team-invitation
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :delete-team-invitation params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn delete-team-webhook
|
||||
[{:keys [id] :as params}]
|
||||
(dm/assert! (uuid? id))
|
||||
(ptk/reify ::delete-team-webhook
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)
|
||||
{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :delete-webhook params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(def valid-mtypes
|
||||
#{"application/json"
|
||||
"application/x-www-form-urlencoded"
|
||||
"application/transit+json"})
|
||||
|
||||
(defn update-team-webhook
|
||||
[{:keys [id uri mtype is-active] :as params}]
|
||||
(dm/assert! (uuid? id))
|
||||
(dm/assert! (contains? valid-mtypes mtype))
|
||||
(dm/assert! (boolean? is-active))
|
||||
(dm/assert! (u/uri? uri))
|
||||
(ptk/reify ::update-team-webhook
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)
|
||||
{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :update-webhook params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn create-team-webhook
|
||||
[{:keys [uri mtype is-active] :as params}]
|
||||
(dm/assert! (contains? valid-mtypes mtype))
|
||||
(dm/assert! (boolean? is-active))
|
||||
(dm/assert! (u/uri? uri))
|
||||
|
||||
(ptk/reify ::create-team-webhook
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (-> params
|
||||
(assoc :team-id team-id)
|
||||
(update :uri str))
|
||||
{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :create-webhook params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: delete-team
|
||||
|
||||
(defn delete-team
|
||||
[{:keys [id] :as params}]
|
||||
(ptk/reify ::delete-team
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :delete-team {:id id})
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: create-project
|
||||
|
||||
(defn- project-created
|
||||
|
@ -724,7 +239,7 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:dashboard-projects id] project)
|
||||
(assoc-in [:projects id] project)
|
||||
(assoc-in [:dashboard-local :project-for-edit] id)))))
|
||||
|
||||
(defn create-project
|
||||
|
@ -732,7 +247,7 @@
|
|||
(ptk/reify ::create-project
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [projects (get state :dashboard-projects)
|
||||
(let [projects (get state :projects)
|
||||
unames (cfh/get-used-names projects)
|
||||
name (cfh/generate-unique-name unames (str (tr "dashboard.new-project-prefix") " 1"))
|
||||
team-id (:current-team-id state)
|
||||
|
@ -753,7 +268,7 @@
|
|||
(ptk/reify ::project-duplicated
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:dashboard-projects id] project))))
|
||||
(assoc-in state [:projects id] project))))
|
||||
|
||||
(defn duplicate-project
|
||||
[{:keys [id name] :as params}]
|
||||
|
@ -803,11 +318,11 @@
|
|||
(ptk/reify ::toggle-project-pin
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:dashboard-projects id :is-pinned] (not is-pinned)))
|
||||
(assoc-in state [:projects id :is-pinned] (not is-pinned)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [project (get-in state [:dashboard-projects id])
|
||||
(let [project (get-in state [:projects id])
|
||||
params (select-keys project [:id :is-pinned :team-id])]
|
||||
(->> (rp/cmd! :update-project-pin params)
|
||||
(rx/ignore))))))
|
||||
|
@ -820,7 +335,7 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update-in [:dashboard-projects id :name] (constantly name))
|
||||
(update-in [:projects id :name] (constantly name))
|
||||
(update :dashboard-local dissoc :project-for-edit)))
|
||||
|
||||
ptk/WatchEvent
|
||||
|
@ -836,7 +351,7 @@
|
|||
(ptk/reify ::delete-project
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :dashboard-projects dissoc id))
|
||||
(update state :projects dissoc id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
@ -850,7 +365,7 @@
|
|||
(ptk/reify ::file-deleted
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:dashboard-projects project-id :count] dec))))
|
||||
(update-in state [:projects project-id :count] dec))))
|
||||
|
||||
(defn delete-file
|
||||
[{:keys [id project-id] :as params}]
|
||||
|
@ -858,9 +373,9 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(d/update-when :dashboard-files dissoc id)
|
||||
(d/update-when :dashboard-shared-files dissoc id)
|
||||
(d/update-when :dashboard-recent-files dissoc id)))
|
||||
(d/update-when :files dissoc id)
|
||||
(d/update-when :shared-files dissoc id)
|
||||
(d/update-when :recent-files dissoc id)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -882,9 +397,9 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(d/update-in-when [:dashboard-files id :name] (constantly name))
|
||||
(d/update-in-when [:dashboard-shared-files id :name] (constantly name))
|
||||
(d/update-in-when [:dashboard-recent-files id :name] (constantly name))))
|
||||
(d/update-in-when [:files id :name] (constantly name))
|
||||
(d/update-in-when [:shared-files id :name] (constantly name))
|
||||
(d/update-in-when [:recent-files id :name] (constantly name))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
@ -906,10 +421,10 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(d/update-in-when [:dashboard-files id :is-shared] (constantly is-shared))
|
||||
(d/update-in-when [:dashboard-recent-files id :is-shared] (constantly is-shared))
|
||||
(d/update-in-when [:files id :is-shared] (constantly is-shared))
|
||||
(d/update-in-when [:recent-files id :is-shared] (constantly is-shared))
|
||||
(cond-> (not is-shared)
|
||||
(d/update-when :dashboard-shared-files dissoc id))))
|
||||
(d/update-when :shared-files dissoc id))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
@ -928,8 +443,8 @@
|
|||
(= file-id (:id %))
|
||||
(assoc :thumbnail-id thumbnail-id)))))]
|
||||
(-> state
|
||||
(d/update-in-when [:dashboard-files file-id] assoc :thumbnail-id thumbnail-id)
|
||||
(d/update-in-when [:dashboard-recent-files file-id] assoc :thumbnail-id thumbnail-id)
|
||||
(d/update-in-when [:files file-id] assoc :thumbnail-id thumbnail-id)
|
||||
(d/update-in-when [:recent-files file-id] assoc :thumbnail-id thumbnail-id)
|
||||
(d/update-when :dashboard-search-result update-search-files))))))
|
||||
|
||||
;; --- EVENT: create-file
|
||||
|
@ -946,9 +461,9 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:dashboard-files id] file)
|
||||
(assoc-in [:dashboard-recent-files id] file)
|
||||
(update-in [:dashboard-projects project-id :count] inc)))))
|
||||
(assoc-in [:files id] file)
|
||||
(assoc-in [:recent-files id] file)
|
||||
(update-in [:projects project-id :count] inc)))))
|
||||
|
||||
(defn create-file
|
||||
[{:keys [project-id name] :as params}]
|
||||
|
@ -963,7 +478,7 @@
|
|||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
|
||||
files (get state :dashboard-files)
|
||||
files (get state :files)
|
||||
unames (cfh/get-used-names files)
|
||||
name (or name (cfh/generate-unique-name unames (str (tr "dashboard.new-file-prefix") " 1")))
|
||||
features (-> (features/get-team-enabled-features state)
|
||||
|
@ -1015,14 +530,14 @@
|
|||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [origin-project (get-in state [:dashboard-files (first ids) :project-id])
|
||||
(let [origin-project (get-in state [:files (first ids) :project-id])
|
||||
update-project (fn [project delta op]
|
||||
(-> project
|
||||
(update :count #(op % (count ids)))
|
||||
(assoc :modified-at (dt/plus (dt/now) {:milliseconds delta}))))]
|
||||
(-> state
|
||||
(d/update-in-when [:dashboard-projects origin-project] update-project 0 -)
|
||||
(d/update-in-when [:dashboard-projects project-id] update-project 10 +))))
|
||||
(d/update-in-when [:projects origin-project] update-project 0 -)
|
||||
(d/update-in-when [:projects project-id] update-project 10 +))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
@ -1063,123 +578,6 @@
|
|||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Navigation
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn go-to-workspace
|
||||
[{:keys [id project-id] :as file}]
|
||||
(ptk/reify ::go-to-workspace
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [pparams {:project-id project-id :file-id id}]
|
||||
(rx/of (rt/nav :workspace pparams))))))
|
||||
|
||||
|
||||
(defn go-to-files
|
||||
([project-id]
|
||||
(ptk/reify ::go-to-files-1
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-files {:team-id team-id
|
||||
:project-id project-id}))))))
|
||||
([team-id project-id]
|
||||
(ptk/reify ::go-to-files-2
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (rt/nav :dashboard-files {:team-id team-id
|
||||
:project-id project-id}))))))
|
||||
|
||||
(defn go-to-search
|
||||
([] (go-to-search nil))
|
||||
([term]
|
||||
(ptk/reify ::go-to-search
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(if (empty? term)
|
||||
(do
|
||||
(dom/focus! (dom/get-element "search-input"))
|
||||
(rx/of (rt/nav :dashboard-search
|
||||
{:team-id team-id})))
|
||||
(rx/of (rt/nav :dashboard-search
|
||||
{:team-id team-id}
|
||||
{:search-term term})))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(dom/focus! (dom/get-element "search-input"))))))
|
||||
|
||||
(defn go-to-projects
|
||||
([]
|
||||
(ptk/reify ::go-to-projects-0
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-projects {:team-id team-id}))))))
|
||||
([team-id]
|
||||
(ptk/reify ::go-to-projects-1
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; FIXME: Remove on 2.5
|
||||
(assoc state :current-team-id team-id))
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (rt/nav :dashboard-projects {:team-id team-id}))))))
|
||||
|
||||
(defn go-to-team-members
|
||||
[]
|
||||
(ptk/reify ::go-to-team-members
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-team-members {:team-id team-id}))))))
|
||||
|
||||
(defn go-to-team-invitations
|
||||
[]
|
||||
(ptk/reify ::go-to-team-invitations
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-team-invitations {:team-id team-id}))))))
|
||||
|
||||
(defn go-to-team-webhooks
|
||||
[]
|
||||
(ptk/reify ::go-to-team-webhooks
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-team-webhooks {:team-id team-id}))))))
|
||||
|
||||
(defn go-to-team-settings
|
||||
[]
|
||||
(ptk/reify ::go-to-team-settings
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-team-settings {:team-id team-id}))))))
|
||||
|
||||
(defn go-to-drafts
|
||||
[]
|
||||
(ptk/reify ::go-to-drafts
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
projects (:dashboard-projects state)
|
||||
default-project (d/seek :is-default (vals projects))]
|
||||
(when default-project
|
||||
(rx/of (rt/nav :dashboard-files {:team-id team-id
|
||||
:project-id (:id default-project)})))))))
|
||||
|
||||
(defn go-to-libs
|
||||
[]
|
||||
(ptk/reify ::go-to-libs
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of (rt/nav :dashboard-libraries {:team-id team-id}))))))
|
||||
|
||||
(defn create-element
|
||||
[]
|
||||
(ptk/reify ::create-element
|
||||
|
@ -1190,10 +588,10 @@
|
|||
pparams (:path-params route)
|
||||
in-project? (contains? pparams :project-id)
|
||||
name (if in-project?
|
||||
(let [files (get state :dashboard-files)
|
||||
(let [files (get state :files)
|
||||
unames (cfh/get-used-names files)]
|
||||
(cfh/generate-unique-name unames (str (tr "dashboard.new-file-prefix") " 1")))
|
||||
(let [projects (get state :dashboard-projects)
|
||||
(let [projects (get state :projects)
|
||||
unames (cfh/get-used-names projects)]
|
||||
(cfh/generate-unique-name unames (str (tr "dashboard.new-project-prefix") " 1"))))
|
||||
params (if in-project?
|
||||
|
@ -1203,7 +601,7 @@
|
|||
:team-id team-id})
|
||||
action-name (if in-project? :create-file :create-project)
|
||||
action (if in-project? file-created project-created)
|
||||
can-edit? (dm/get-in state [:permissions :can-edit])]
|
||||
can-edit? (dm/get-in state [:teams team-id :permissions :can-edit])]
|
||||
|
||||
(when can-edit?
|
||||
(->> (rp/cmd! action-name params)
|
||||
|
@ -1214,10 +612,9 @@
|
|||
(ptk/reify ::open-selected-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [files (get-in state [:dashboard-local :selected-files])]
|
||||
(let [[file-id :as files] (get state :selected-files)]
|
||||
(if (= 1 (count files))
|
||||
(let [file (get-in state [:dashboard-files (first files)])]
|
||||
(rx/of (go-to-workspace file)))
|
||||
(rx/of (dcm/go-to-workspace :file-id file-id))
|
||||
(rx/empty))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1229,13 +626,13 @@
|
|||
(ptk/reify ::handle-change-team-role
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dc/change-team-role params)
|
||||
(rx/of (dcm/change-team-role params)
|
||||
(modal/hide)))))
|
||||
|
||||
(defn- process-message
|
||||
[{:keys [type] :as msg}]
|
||||
(case type
|
||||
:notification (dc/handle-notification msg)
|
||||
:notification (dcm/handle-notification msg)
|
||||
:team-role-change (handle-change-team-role msg)
|
||||
:team-membership-change (dc/team-membership-change msg)
|
||||
:team-membership-change (dcm/team-membership-change msg)
|
||||
nil))
|
||||
|
|
|
@ -6,27 +6,28 @@
|
|||
|
||||
(ns app.main.data.dashboard.shortcuts
|
||||
(:require
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.data.shortcuts :as ds]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.store :as st]))
|
||||
|
||||
(def shortcuts
|
||||
{:go-to-search {:tooltip (ds/meta "F")
|
||||
:command (ds/c-mod "f")
|
||||
:subsections [:navigation-dashboard]
|
||||
:fn #(st/emit! (dd/go-to-search))}
|
||||
:fn #(st/emit! (dcm/go-to-dashboard-search))}
|
||||
|
||||
:go-to-drafts {:tooltip "G D"
|
||||
:command "g d"
|
||||
:subsections [:navigation-dashboard]
|
||||
:fn #(st/emit! (dd/go-to-drafts))}
|
||||
:fn #(st/emit! (dcm/go-to-dashboard-files :project-id :default))}
|
||||
|
||||
:go-to-libs {:tooltip "G L"
|
||||
:command "g l"
|
||||
:subsections [:navigation-dashboard]
|
||||
:fn #(st/emit! (dd/go-to-libs))}
|
||||
:fn #(st/emit! (dcm/go-to-dashboard-libraries))}
|
||||
|
||||
:create-new-project {:tooltip "+"
|
||||
:command "+"
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.data.events
|
||||
(ns app.main.data.event
|
||||
(:require
|
||||
["ua-parser-js" :as ua]
|
||||
[app.common.data :as d]
|
||||
|
@ -182,7 +182,7 @@
|
|||
(rx/filter #(pos? (count %)))
|
||||
(rx/debounce 2000))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? :app.main.data.users/logout))
|
||||
(rx/filter (ptk/type? :app.main.data.profile/logout))
|
||||
(rx/observe-on :async)))
|
||||
(rx/map (fn [_]
|
||||
(into [] (take max-buffer-size) @buffer)))
|
|
@ -7,7 +7,7 @@
|
|||
(ns app.main.data.exports.assets
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.persistence :as dwp]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
[app.common.logging :as log]
|
||||
[app.common.media :as cm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.repo :as rp]
|
||||
|
@ -59,10 +59,10 @@
|
|||
(adapt-font-id [variant]
|
||||
(update variant :font-id #(str "custom-" %)))]
|
||||
|
||||
(ptk/reify ::team-fonts-loaded
|
||||
(ptk/reify ::fonts-loaded
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :dashboard-fonts (d/index-by :id fonts)))
|
||||
(assoc state :fonts (d/index-by :id fonts)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
|
@ -72,13 +72,14 @@
|
|||
(mapv prepare-font))]
|
||||
(fonts/register! :custom fonts))))))
|
||||
|
||||
(defn load-team-fonts
|
||||
[team-id]
|
||||
(defn fetch-fonts
|
||||
[]
|
||||
(ptk/reify ::load-team-fonts
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :get-font-variants {:team-id team-id})
|
||||
(rx/map fonts-fetched)))))
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/cmd! :get-font-variants {:team-id team-id})
|
||||
(rx/map fonts-fetched))))))
|
||||
|
||||
(defn process-upload
|
||||
"Given a seq of blobs and the team id, creates a ready-to-use fonts
|
||||
|
@ -90,12 +91,15 @@
|
|||
variant (or (.getEnglishName ^js font "preferredSubfamily")
|
||||
(.getEnglishName ^js font "fontSubfamily"))
|
||||
|
||||
;; Vertical metrics determine the baseline in a text and the space between lines of text.
|
||||
;; For historical reasons, there are three pairs of ascender/descender values, known as hhea, OS/2 and uSWin metrics.
|
||||
;; Depending on the font, operating system and application a different set will be used to render text on the screen.
|
||||
;; On Mac, Safari and Chrome use the hhea values to render text. Firefox will respect the useTypoMetrics setting and will use the OS/2 if it is set.
|
||||
;; If the useTypoMetrics is not set, Firefox will also use metrics from the hhea table.
|
||||
;; On Windows, all browsers use the usWin metrics, but respect the useTypoMetrics setting and if set will use the OS/2 values.
|
||||
;; Vertical metrics determine the baseline in a text and the space between lines of
|
||||
;; text. For historical reasons, there are three pairs of ascender/descender
|
||||
;; values, known as hhea, OS/2 and uSWin metrics. Depending on the font, operating
|
||||
;; system and application a different set will be used to render text on the
|
||||
;; screen. On Mac, Safari and Chrome use the hhea values to render text. Firefox
|
||||
;; will respect the useTypoMetrics setting and will use the OS/2 if it is set. If
|
||||
;; the useTypoMetrics is not set, Firefox will also use metrics from the hhea
|
||||
;; table. On Windows, all browsers use the usWin metrics, but respect the
|
||||
;; useTypoMetrics setting and if set will use the OS/2 values.
|
||||
|
||||
hhea-ascender (abs (-> ^js font .-tables .-hhea .-ascender))
|
||||
hhea-descender (abs (-> ^js font .-tables .-hhea .-descender))
|
||||
|
@ -239,7 +243,7 @@
|
|||
(ptk/reify ::add-font
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :dashboard-fonts assoc (:id font) font))
|
||||
(update state :fonts assoc (:id font) font))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -260,13 +264,10 @@
|
|||
(update [_ state]
|
||||
;; Update all variants that has the same font-id with the new
|
||||
;; name in the local state.
|
||||
(update state :dashboard-fonts
|
||||
(fn [fonts]
|
||||
(d/mapm (fn [_ font]
|
||||
(cond-> font
|
||||
(= id (:font-id font))
|
||||
(assoc :font-family name)))
|
||||
fonts))))
|
||||
(update state :fonts update-vals (fn [font]
|
||||
(cond-> font
|
||||
(= id (:font-id font))
|
||||
(assoc :font-family name)))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -285,10 +286,11 @@
|
|||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :dashboard-fonts
|
||||
(update state :fonts
|
||||
(fn [variants]
|
||||
(d/removem (fn [[_id variant]]
|
||||
(= (:font-id variant) font-id)) variants))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
|
@ -305,7 +307,7 @@
|
|||
(ptk/reify ::delete-font-variants
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :dashboard-fonts
|
||||
(update state :fonts
|
||||
(fn [variants]
|
||||
(d/removem (fn [[_ variant]]
|
||||
(= (:id variant) id))
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:refer-clojure :exclude [update])
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.store :as st]
|
||||
[cljs.core :as c]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
(rx/merge
|
||||
(let [stopper (rx/filter (ptk/type? ::hide) stream)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? :app.util.router/navigate))
|
||||
(rx/filter (ptk/type? :app.main.router/navigate))
|
||||
(rx/map (fn [_] (hide)))
|
||||
(rx/take-until stopper)))
|
||||
(when (:timeout data)
|
||||
|
|
|
@ -4,24 +4,21 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.data.users
|
||||
(ns app.main.data.profile
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.media :as di]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.websocket :as ws]
|
||||
[app.main.features :as features]
|
||||
[app.main.data.team :as-alias dtm]
|
||||
[app.main.repo :as rp]
|
||||
[app.plugins.register :as register]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.main.router :as rt]
|
||||
[app.plugins.register :as plugins.register]
|
||||
[app.util.i18n :as i18n]
|
||||
[app.util.storage :as storage]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
@ -40,7 +37,7 @@
|
|||
[:lang {:optional true} :string]
|
||||
[:theme {:optional true} :string]])
|
||||
|
||||
(def check-profile!
|
||||
(def check-profile
|
||||
(sm/check-fn schema:profile))
|
||||
|
||||
;; --- HELPERS
|
||||
|
@ -49,98 +46,31 @@
|
|||
[{:keys [id]}]
|
||||
(and (uuid? id) (not= id uuid/zero)))
|
||||
|
||||
(defn get-current-team-id
|
||||
[profile]
|
||||
(let [team-id (::current-team-id storage/user)]
|
||||
(or team-id (:default-team-id profile))))
|
||||
|
||||
(defn set-current-team!
|
||||
[team-id]
|
||||
(if (nil? team-id)
|
||||
(swap! storage/user dissoc ::current-team-id)
|
||||
(swap! storage/user assoc ::current-team-id team-id)))
|
||||
|
||||
;; --- EVENT: fetch-teams
|
||||
|
||||
(defn teams-fetched
|
||||
[teams]
|
||||
(ptk/reify ::teams-fetched
|
||||
IDeref
|
||||
(-deref [_] teams)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :teams (d/index-by :id teams)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
;; Check if current team-id is part of available teams
|
||||
;; if not, dissoc it from storage.
|
||||
|
||||
(let [ids (into #{} (map :id) teams)]
|
||||
(when-let [ctid (::current-team-id storage/user)]
|
||||
(when-not (contains? ids ctid)
|
||||
(swap! storage/user dissoc ::current-team-id)))))))
|
||||
|
||||
(defn fetch-teams
|
||||
[]
|
||||
(ptk/reify ::fetch-teams
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :get-teams)
|
||||
(rx/map teams-fetched)))))
|
||||
|
||||
(defn set-current-team
|
||||
[team]
|
||||
(ptk/reify ::set-current-team
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :team team)
|
||||
(assoc :permissions (:permissions team))
|
||||
(assoc :current-team-id (:id team))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (features/initialize (:features team #{}))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(set-current-team! (:id team)))))
|
||||
|
||||
;; --- EVENT: fetch-profile
|
||||
|
||||
(declare logout)
|
||||
|
||||
(def profile-fetched?
|
||||
(ptk/type? ::profile-fetched))
|
||||
|
||||
(defn profile-fetched
|
||||
(defn initialize-profile
|
||||
"Initialize profile state, only logged-in profile data should be
|
||||
passed to this event"
|
||||
[{:keys [id] :as profile}]
|
||||
(ptk/reify ::profile-fetched
|
||||
(ptk/reify ::initialize-profile
|
||||
IDeref
|
||||
(-deref [_] profile)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(cond-> state
|
||||
(is-authenticated? profile)
|
||||
(-> (assoc :profile-id id)
|
||||
(assoc :profile profile))))
|
||||
(-> state
|
||||
(assoc :profile-id id)
|
||||
(assoc :profile profile)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [profile (:profile state)
|
||||
email (:email profile)
|
||||
previous-profile (:profile storage/user)
|
||||
previous-email (:email previous-profile)]
|
||||
(when profile
|
||||
(swap! storage/user assoc :profile profile)
|
||||
(i18n/set-locale! (:lang profile))
|
||||
(when (not= previous-email email)
|
||||
(set-current-team! nil))
|
||||
(let [profile (:profile state)]
|
||||
(swap! storage/user assoc :profile profile)
|
||||
(i18n/set-locale! (:lang profile))
|
||||
(plugins.register/init)))))
|
||||
|
||||
(register/init))))))
|
||||
(def profile-fetched?
|
||||
(ptk/type? ::profile-fetched))
|
||||
|
||||
(defn- on-fetch-profile-exception
|
||||
[cause]
|
||||
|
@ -160,213 +90,9 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :get-profile)
|
||||
(rx/map profile-fetched)
|
||||
(rx/map (partial ptk/data-event ::profile-fetched))
|
||||
(rx/catch on-fetch-profile-exception)))))
|
||||
|
||||
;; --- EVENT: login
|
||||
|
||||
(defn- logged-in
|
||||
"This is the main event that is executed once we have logged in
|
||||
profile. The profile can proceed from standard login or from
|
||||
accepting invitation, or third party auth signup or singin."
|
||||
[profile]
|
||||
(letfn [(get-redirect-events []
|
||||
(let [team-id (get-current-team-id profile)
|
||||
welcome-file-id (dm/get-in profile [:props :welcome-file-id])
|
||||
redirect-href (:login-redirect @storage/session)
|
||||
current-href (rt/get-current-href)]
|
||||
|
||||
(cond
|
||||
(some? redirect-href)
|
||||
(binding [storage/*sync* true]
|
||||
(swap! storage/session dissoc :login-redirect)
|
||||
(if (= current-href redirect-href)
|
||||
(rx/of (rt/reload true))
|
||||
(rx/of (rt/nav-raw :href redirect-href))))
|
||||
|
||||
(some? welcome-file-id)
|
||||
(rx/of (rt/nav' :workspace {:project-id (:default-project-id profile)
|
||||
:file-id welcome-file-id})
|
||||
(update-profile-props {:welcome-file-id nil}))
|
||||
|
||||
:else
|
||||
(rx/of (rt/nav' :dashboard-projects {:team-id team-id})))))]
|
||||
|
||||
(ptk/reify ::logged-in
|
||||
ev/Event
|
||||
(-data [_]
|
||||
{::ev/name "signin"
|
||||
::ev/type "identify"
|
||||
:email (:email profile)
|
||||
:auth-backend (:auth-backend profile)
|
||||
:fullname (:fullname profile)
|
||||
:is-muted (:is-muted profile)
|
||||
:default-team-id (:default-team-id profile)
|
||||
:default-project-id (:default-project-id profile)})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(when (is-authenticated? profile)
|
||||
(->> (rx/concat
|
||||
(rx/of (profile-fetched profile)
|
||||
(fetch-teams)
|
||||
(ws/initialize))
|
||||
(get-redirect-events))
|
||||
(rx/observe-on :async)))))))
|
||||
|
||||
(declare login-from-register)
|
||||
|
||||
(defn login
|
||||
[{:keys [email password invitation-token] :as data}]
|
||||
(ptk/reify ::login
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error rx/throw
|
||||
on-success identity}} (meta data)
|
||||
|
||||
params {:email email
|
||||
:password password
|
||||
:invitation-token invitation-token}]
|
||||
|
||||
;; NOTE: We can't take the profile value from login because
|
||||
;; there are cases when login is successful but the cookie is
|
||||
;; not set properly (because of possible misconfiguration).
|
||||
;; So, we proceed to make an additional call to fetch the
|
||||
;; profile, and ensure that cookie is set correctly. If
|
||||
;; profile fetch is successful, we mark the user logged in, if
|
||||
;; the returned profile is an NOT authenticated profile, we
|
||||
;; proceed to logout and show an error message.
|
||||
|
||||
(->> (rp/cmd! :login-with-password (d/without-nils params))
|
||||
(rx/merge-map (fn [data]
|
||||
(rx/merge
|
||||
(rx/of (fetch-profile))
|
||||
(->> stream
|
||||
(rx/filter profile-fetched?)
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/filter (complement is-authenticated?))
|
||||
(rx/tap on-error)
|
||||
(rx/map #(ex/raise :type :authentication))
|
||||
(rx/observe-on :async))
|
||||
|
||||
(->> stream
|
||||
(rx/filter profile-fetched?)
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/filter is-authenticated?)
|
||||
(rx/map (fn [profile]
|
||||
(with-meta (merge data profile)
|
||||
{::ev/source "login"})))
|
||||
(rx/tap on-success)
|
||||
(rx/map logged-in)
|
||||
(rx/observe-on :async)))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(def ^:private schema:login-with-ldap
|
||||
[:map {:title "login-with-ldap"}
|
||||
[:email ::sm/email]
|
||||
[:password :string]])
|
||||
|
||||
(defn login-with-ldap
|
||||
[params]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid params"
|
||||
(sm/check schema:login-with-ldap params))
|
||||
|
||||
(ptk/reify ::login-with-ldap
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error rx/throw
|
||||
on-success identity}} (meta params)]
|
||||
(->> (rp/cmd! :login-with-ldap params)
|
||||
(rx/tap on-success)
|
||||
(rx/map (fn [profile]
|
||||
(-> profile
|
||||
(with-meta {::ev/source "login-with-ldap"})
|
||||
(logged-in))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn login-from-token
|
||||
"Used mainly as flow continuation after token validation."
|
||||
[{:keys [profile] :as tdata}]
|
||||
(ptk/reify ::login-from-token
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rx/of (logged-in (with-meta profile {::ev/source "login-with-token"})))
|
||||
;; NOTE: we need this to be asynchronous because the effect
|
||||
;; should be called before proceed with the login process
|
||||
(rx/observe-on :async)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(set-current-team! nil))))
|
||||
|
||||
(defn login-from-register
|
||||
"Event used mainly for mark current session as logged-in in after the
|
||||
user successfully registered using third party auth provider (in this
|
||||
case we dont need to verify the email)."
|
||||
[]
|
||||
(ptk/reify ::login-from-register
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(rx/merge
|
||||
(rx/of (fetch-profile))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::profile-fetched))
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/map (fn [profile]
|
||||
(with-meta profile
|
||||
{::ev/source "register"})))
|
||||
(rx/map logged-in)
|
||||
(rx/observe-on :async))))))
|
||||
|
||||
;; --- EVENT: logout
|
||||
|
||||
(defn logged-out
|
||||
([] (logged-out {}))
|
||||
([_params]
|
||||
(ptk/reify ::logged-out
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(select-keys state [:route :router :session-id :history]))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/merge
|
||||
;; NOTE: We need the `effect` of the current event to be
|
||||
;; executed before the redirect.
|
||||
(->> (rx/of (rt/nav :auth-login))
|
||||
(rx/observe-on :async))
|
||||
(rx/of (ws/finalize))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
;; We prefer to keek some stuff in the storage like the current-team-id and the profile
|
||||
(swap! storage/user (constantly {}))))))
|
||||
|
||||
(defn logout
|
||||
([] (logout {}))
|
||||
([params]
|
||||
(ptk/reify ::logout
|
||||
ev/Event
|
||||
(-data [_] {})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [profile-id (:profile-id state)]
|
||||
(->> (rx/interval 500)
|
||||
(rx/take 1)
|
||||
(rx/mapcat (fn [_]
|
||||
(->> (rp/cmd! :logout {:profile-id profile-id})
|
||||
(rx/delay-at-least 300)
|
||||
(rx/catch (constantly (rx/of 1))))))
|
||||
(rx/map #(logged-out params))))))))
|
||||
|
||||
;; --- Update Profile
|
||||
|
||||
(defn persist-profile
|
||||
|
@ -386,7 +112,7 @@
|
|||
[data]
|
||||
(dm/assert!
|
||||
"expected valid profile data"
|
||||
(check-profile! data))
|
||||
(check-profile data))
|
||||
|
||||
(ptk/reify ::update-profile
|
||||
ptk/WatchEvent
|
||||
|
@ -589,6 +315,9 @@
|
|||
|
||||
;; --- EVENT: request-account-deletion
|
||||
|
||||
(def profile-deleted?
|
||||
(ptk/type? ::profile-deleted))
|
||||
|
||||
(defn request-account-deletion
|
||||
[params]
|
||||
(ptk/reify ::request-account-deletion
|
||||
|
@ -599,7 +328,8 @@
|
|||
on-success identity}} (meta params)]
|
||||
(->> (rp/cmd! :delete-profile {})
|
||||
(rx/tap on-success)
|
||||
(rx/map logged-out)
|
||||
(rx/map (fn [_]
|
||||
(ptk/data-event ::profile-deleted params)))
|
||||
(rx/catch on-error)
|
||||
(rx/delay-at-least 300))))))
|
||||
|
||||
|
@ -652,16 +382,6 @@
|
|||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: crete-demo-profile
|
||||
|
||||
(defn create-demo-profile
|
||||
[]
|
||||
(ptk/reify ::create-demo-profile
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :create-demo-profile {})
|
||||
(rx/map login)))))
|
||||
|
||||
;; --- EVENT: fetch-team-webhooks
|
||||
|
||||
(defn access-tokens-fetched
|
||||
|
@ -716,28 +436,3 @@
|
|||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn show-redirect-error
|
||||
"A helper event that interprets the OIDC redirect errors on the URI
|
||||
and shows an appropriate error message using the notification
|
||||
banners."
|
||||
[error]
|
||||
(ptk/reify ::show-redirect-error
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(when-let [hint (case error
|
||||
"registration-disabled"
|
||||
(tr "errors.registration-disabled")
|
||||
"profile-blocked"
|
||||
(tr "errors.profile-blocked")
|
||||
"auth-provider-not-allowed"
|
||||
(tr "errors.auth-provider-not-allowed")
|
||||
"email-domain-not-allowed"
|
||||
(tr "errors.email-domain-not-allowed")
|
||||
|
||||
;; We explicitly do not show any error here, it a explicit user operation.
|
||||
"unable-to-auth"
|
||||
nil
|
||||
|
||||
(tr "errors.generic"))]
|
||||
|
||||
(rx/of (ntf/warn hint))))))
|
80
frontend/src/app/main/data/project.cljs
Normal file
80
frontend/src/app/main/data/project.cljs
Normal file
|
@ -0,0 +1,80 @@
|
|||
;; 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.project
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.logging :as log]
|
||||
[app.main.repo :as rp]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(log/set-level! :warn)
|
||||
|
||||
(defn- project-fetched
|
||||
[{:keys [id] :as project}]
|
||||
(ptk/reify ::project-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:projects id] merge project))))
|
||||
|
||||
(defn fetch-project
|
||||
"Fetch or refresh a single project"
|
||||
([] (fetch-project))
|
||||
([project-id]
|
||||
(assert (uuid? project-id) "expected a valid uuid for `project-id`")
|
||||
|
||||
(ptk/reify ::fetch-project
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [project-id (or project-id (:current-project-id state))]
|
||||
(->> (rp/cmd! :get-project {:id project-id})
|
||||
(rx/map project-fetched)))))))
|
||||
|
||||
(defn initialize-project
|
||||
[project-id]
|
||||
(ptk/reify ::initialize-project
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :current-project-id project-id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (fetch-project project-id)))))
|
||||
|
||||
(defn finalize-project
|
||||
[project-id]
|
||||
(ptk/reify ::finalize-project
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [project-id' (get state :current-project-id)]
|
||||
(if (= project-id' project-id)
|
||||
(dissoc state :current-project-id)
|
||||
state)))))
|
||||
|
||||
|
||||
(defn- files-fetched
|
||||
[project-id files]
|
||||
(ptk/reify ::files-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update :files merge (d/index-by :id files))
|
||||
(d/update-in-when [:projects project-id] (fn [project]
|
||||
(assoc project :count (count files))))))))
|
||||
|
||||
(defn fetch-files
|
||||
[project-id]
|
||||
(assert (uuid? project-id) "expected valid uuid for `project-id`")
|
||||
(ptk/reify ::fetch-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :get-project-files {:project-id project-id})
|
||||
(rx/map (partial files-fetched project-id))))))
|
||||
|
||||
|
||||
|
||||
|
559
frontend/src/app/main/data/team.cljs
Normal file
559
frontend/src/app/main/data/team.cljs
Normal file
|
@ -0,0 +1,559 @@
|
|||
;; 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.team
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.logging :as log]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.team :as ctt]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.media :as di]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.util.storage :as storage]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(log/set-level! :warn)
|
||||
|
||||
(defn get-last-team-id
|
||||
"Get last accessed team id"
|
||||
[]
|
||||
(::current-team-id storage/global))
|
||||
|
||||
(defn teams-fetched
|
||||
[teams]
|
||||
(ptk/reify ::teams-fetched
|
||||
IDeref
|
||||
(-deref [_] teams)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(reduce (fn [state {:keys [id] :as team}]
|
||||
(update-in state [:teams id] merge team))
|
||||
state
|
||||
teams))))
|
||||
|
||||
(defn fetch-teams
|
||||
[]
|
||||
(ptk/reify ::fetch-teams
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :get-teams)
|
||||
(rx/map teams-fetched)))))
|
||||
|
||||
;; --- EVENT: fetch-members
|
||||
|
||||
(defn- members-fetched
|
||||
[team-id members]
|
||||
(ptk/reify ::members-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(update-in [:teams team-id] assoc :members members)
|
||||
(update :profiles merge (d/index-by :id members))))))
|
||||
|
||||
(defn fetch-members
|
||||
[]
|
||||
(ptk/reify ::fetch-members
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/cmd! :get-team-members {:team-id team-id})
|
||||
(rx/map (partial members-fetched team-id)))))))
|
||||
|
||||
(defn- invitations-fetched
|
||||
[team-id invitations]
|
||||
(ptk/reify ::invitations-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:teams team-id] assoc :invitations invitations))))
|
||||
|
||||
(defn fetch-invitations
|
||||
[]
|
||||
(ptk/reify ::fetch-invitations
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/cmd! :get-team-invitations {:team-id team-id})
|
||||
(rx/map (partial invitations-fetched team-id)))))))
|
||||
|
||||
(defn set-current-team
|
||||
[{:keys [id permissions features] :as team}]
|
||||
(ptk/reify ::set-current-team
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
;; FIXME: redundant operation, only necessary on workspace
|
||||
;; until workspace initialization is refactored
|
||||
(update-in [:teams id] merge team)
|
||||
(assoc :permissions permissions)
|
||||
;; FIXME: this is a redundant operation that only needed by
|
||||
;; workspace; ti will not be needed after workspace
|
||||
;; bootstrap & urls refactor
|
||||
(assoc :current-team-id id)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (features/initialize (or features #{}))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(swap! storage/global assoc ::current-team-id id))))
|
||||
|
||||
(defn- team-initialized
|
||||
[]
|
||||
(ptk/reify ::team-initialized
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
teams (get state :teams)
|
||||
team (get teams team-id)]
|
||||
(rx/of (set-current-team team)
|
||||
(fetch-members))))))
|
||||
|
||||
(defn initialize-team
|
||||
[team-id]
|
||||
(ptk/reify ::initialize-team
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :current-team-id team-id))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [stopper (rx/filter (ptk/type? ::finalize-team) stream)]
|
||||
(->> (rx/merge
|
||||
(rx/of (fetch-teams))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::teams-fetched))
|
||||
(rx/observe-on :async)
|
||||
(rx/map team-initialized)))
|
||||
(rx/take-until stopper))))))
|
||||
|
||||
(defn finalize-team
|
||||
[team-id]
|
||||
(ptk/reify ::finalize-team
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [team-id' (get state :current-team-id)]
|
||||
(if (= team-id' team-id)
|
||||
(-> state
|
||||
(dissoc :current-team-id)
|
||||
(dissoc :shared-files)
|
||||
(dissoc :fonts))
|
||||
state)))))
|
||||
|
||||
;; --- ROLES
|
||||
|
||||
(defn update-member-role
|
||||
[{:keys [role member-id] :as params}]
|
||||
(dm/assert! (uuid? member-id))
|
||||
(dm/assert! (contains? ctt/valid-roles role))
|
||||
|
||||
(ptk/reify ::update-member-role
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/cmd! :update-team-member-role params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (fetch-members)
|
||||
(fetch-teams)
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "update-team-member-role"
|
||||
:team-id team-id
|
||||
:role role
|
||||
:member-id member-id})))))))))
|
||||
|
||||
(defn delete-member
|
||||
[{:keys [member-id] :as params}]
|
||||
(dm/assert! (uuid? member-id))
|
||||
(ptk/reify ::delete-member
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)]
|
||||
(->> (rp/cmd! :delete-team-member params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (fetch-members)
|
||||
(fetch-teams)
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "delete-team-member"
|
||||
:team-id team-id
|
||||
:member-id member-id})))))))))
|
||||
|
||||
|
||||
(defn- stats-fetched
|
||||
[team-id stats]
|
||||
(ptk/reify ::stats-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:teams team-id] assoc :stats stats))))
|
||||
|
||||
(defn fetch-stats
|
||||
[]
|
||||
(ptk/reify ::fetch-stats
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/cmd! :get-team-stats {:team-id team-id})
|
||||
(rx/map (partial stats-fetched team-id)))))))
|
||||
|
||||
(defn- webhooks-fetched
|
||||
[team-id webhooks]
|
||||
(ptk/reify ::webhooks-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:team-id team-id] assoc :webhooks webhooks))))
|
||||
|
||||
(defn fetch-webhooks
|
||||
[]
|
||||
(ptk/reify ::fetch-webhooks
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/cmd! :get-webhooks {:team-id team-id})
|
||||
(rx/map (partial webhooks-fetched team-id)))))))
|
||||
|
||||
(defn- shared-files-fetched
|
||||
[files]
|
||||
(ptk/reify ::shared-files-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [files (d/index-by :id files)]
|
||||
(assoc state :shared-files files)))))
|
||||
|
||||
(defn fetch-shared-files
|
||||
"Event mainly used for fetch a list of shared libraries for a team,
|
||||
this list does not includes the content of the library per se. It
|
||||
is used mainly for show available libraries and a summary of it."
|
||||
[]
|
||||
(ptk/reify ::fetch-shared-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(->> (rp/cmd! :get-team-shared-files {:team-id team-id})
|
||||
(rx/map shared-files-fetched))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Modification
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn update-team-photo
|
||||
[file]
|
||||
(dm/assert!
|
||||
"expected a valid blob for `file` param"
|
||||
(di/blob? file))
|
||||
(ptk/reify ::update-team-photo
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [on-success di/notify-finished-loading
|
||||
on-error #(do (di/notify-finished-loading)
|
||||
(di/process-error %))
|
||||
team-id (:current-team-id state)
|
||||
prepare #(hash-map :file % :team-id team-id)]
|
||||
|
||||
(di/notify-start-loading)
|
||||
(->> (rx/of file)
|
||||
(rx/map di/validate-file)
|
||||
(rx/map prepare)
|
||||
(rx/mapcat #(rp/cmd! :update-team-photo %))
|
||||
(rx/tap on-success)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (fetch-teams)
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "update-team-photo"
|
||||
:team-id team-id}))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
|
||||
;; --- EVENT: create-team
|
||||
|
||||
(defn- team-created
|
||||
[team]
|
||||
(ptk/reify ::team-created
|
||||
IDeref
|
||||
(-deref [_] team)))
|
||||
|
||||
(defn create-team
|
||||
[{:keys [name] :as params}]
|
||||
(dm/assert! (string? name))
|
||||
(ptk/reify ::create-team
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
features (features/get-enabled-features state)
|
||||
params {:name name :features features}]
|
||||
(->> (rp/cmd! :create-team (with-meta params (meta it)))
|
||||
(rx/tap on-success)
|
||||
(rx/map team-created)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
;; --- EVENT: create-team-with-invitations
|
||||
|
||||
(defn create-team-with-invitations
|
||||
[{:keys [name emails role] :as params}]
|
||||
(ptk/reify ::create-team-with-invitations
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
features (features/get-enabled-features state)
|
||||
params {:name name
|
||||
:emails emails
|
||||
:role role
|
||||
:features features}]
|
||||
(->> (rp/cmd! :create-team-with-invitations (with-meta params (meta it)))
|
||||
(rx/tap on-success)
|
||||
(rx/map team-created)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn update-team
|
||||
[{:keys [id name] :as params}]
|
||||
(ptk/reify ::update-team
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:teams id :name] name))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :update-team params)
|
||||
(rx/ignore)))))
|
||||
|
||||
(defn- team-leaved
|
||||
[{:keys [id] :as params}]
|
||||
(ptk/reify ::team-leaved
|
||||
IDeref
|
||||
(-deref [_] params)
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :teams dissoc id))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [teams (get state :teams)]
|
||||
(when-let [ctid (::current-team-id storage/user)]
|
||||
(when-not (contains? teams ctid)
|
||||
(swap! storage/user dissoc ::current-team-id)))))))
|
||||
|
||||
(defn leave-current-team
|
||||
"High-level event for leave team, mainly executed from the
|
||||
dashboard. It automatically redirects user to the default team, once
|
||||
the team-leave operation succeed"
|
||||
[{:keys [reassign-to] :as params}]
|
||||
|
||||
(when reassign-to
|
||||
(assert (uuid? reassign-to) "expect a valid uuid for `reassign-to`"))
|
||||
|
||||
(ptk/reify ::leave-current-team
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (get state :current-team-id)
|
||||
params (assoc params :id team-id)
|
||||
|
||||
{:keys [on-error on-success]
|
||||
:or {on-success rx/empty
|
||||
on-error rx/throw}}
|
||||
(meta params)]
|
||||
|
||||
(->> (rp/cmd! :leave-team params)
|
||||
(rx/mapcat
|
||||
(fn [_]
|
||||
(rx/merge
|
||||
(rx/of (team-leaved params)
|
||||
(fetch-teams)
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "leave-team"
|
||||
:reassign-to reassign-to
|
||||
:team-id team-id}))
|
||||
(on-success))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn create-invitations
|
||||
[{:keys [emails role team-id resend?] :as params}]
|
||||
(dm/assert! (keyword? role))
|
||||
(dm/assert! (uuid? team-id))
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid set of emails"
|
||||
(sm/check-set-of-emails! emails))
|
||||
|
||||
(ptk/reify ::create-invitations
|
||||
ev/Event
|
||||
(-data [_]
|
||||
{:role role
|
||||
:team-id team-id
|
||||
:resend resend?})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [it _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
params (dissoc params :resend?)]
|
||||
(->> (rp/cmd! :create-team-invitations (with-meta params (meta it)))
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn copy-invitation-link
|
||||
[{:keys [email team-id] :as params}]
|
||||
(dm/assert!
|
||||
"expected a valid email"
|
||||
(sm/check-email! email))
|
||||
|
||||
(dm/assert! (uuid? team-id))
|
||||
|
||||
(ptk/reify ::copy-invitation-link
|
||||
IDeref
|
||||
(-deref [_] {:email email :team-id team-id})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)
|
||||
router (:router state)]
|
||||
|
||||
(->> (rp/cmd! :get-team-invitation-token params)
|
||||
(rx/map (fn [params]
|
||||
(rt/resolve router :auth-verify-token params)))
|
||||
(rx/map (fn [fragment]
|
||||
(assoc cf/public-uri :fragment fragment)))
|
||||
(rx/tap (fn [uri]
|
||||
(wapi/write-to-clipboard (str uri))))
|
||||
(rx/tap on-success)
|
||||
(rx/ignore)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn update-invitation-role
|
||||
[{:keys [email team-id role] :as params}]
|
||||
(dm/assert!
|
||||
"expected a valid email"
|
||||
(sm/check-email! email))
|
||||
|
||||
(dm/assert! (uuid? team-id))
|
||||
(dm/assert! (contains? ctt/valid-roles role))
|
||||
|
||||
(ptk/reify ::update-invitation-role
|
||||
IDeref
|
||||
(-deref [_] {:role role})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :update-team-invitation-role params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn delete-invitation
|
||||
[{:keys [email team-id] :as params}]
|
||||
(dm/assert! (sm/check-email! email))
|
||||
(dm/assert! (uuid? team-id))
|
||||
(ptk/reify ::delete-invitation
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :delete-team-invitation params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn delete-team
|
||||
[{:keys [id] :as params}]
|
||||
(ptk/reify ::delete-team
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success rx/empty
|
||||
on-error rx/throw}}
|
||||
(meta params)]
|
||||
|
||||
(->> (rp/cmd! :delete-team {:id id})
|
||||
(rx/mapcat on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn delete-webhook
|
||||
[{:keys [id] :as params}]
|
||||
(dm/assert! (uuid? id))
|
||||
|
||||
(ptk/reify ::delete-webhook
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)
|
||||
{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :delete-webhook params)
|
||||
(rx/tap on-success)
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(def valid-mtypes
|
||||
#{"application/json"
|
||||
"application/x-www-form-urlencoded"
|
||||
"application/transit+json"})
|
||||
|
||||
(defn update-webhook
|
||||
[{:keys [id uri mtype is-active] :as params}]
|
||||
(dm/assert! (uuid? id))
|
||||
(dm/assert! (contains? valid-mtypes mtype))
|
||||
(dm/assert! (boolean? is-active))
|
||||
(dm/assert! (u/uri? uri))
|
||||
|
||||
(ptk/reify ::update-webhook
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (assoc params :team-id team-id)
|
||||
{:keys [on-success on-error]
|
||||
:or {on-success rx/empty
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :update-webhook params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/concat
|
||||
(on-success)
|
||||
(rx/of (fetch-webhooks)))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
(defn create-webhook
|
||||
[{:keys [uri mtype is-active] :as params}]
|
||||
(dm/assert! (contains? valid-mtypes mtype))
|
||||
(dm/assert! (boolean? is-active))
|
||||
(dm/assert! (u/uri? uri))
|
||||
|
||||
(ptk/reify ::create-webhook
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
params (-> params
|
||||
(assoc :team-id team-id)
|
||||
(update :uri str))
|
||||
{:keys [on-success on-error]
|
||||
:or {on-success rx/empty
|
||||
on-error rx/throw}} (meta params)]
|
||||
(->> (rp/cmd! :create-webhook params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/concat
|
||||
(on-success)
|
||||
(rx/of (fetch-webhooks)))))
|
||||
(rx/catch on-error))))))
|
||||
|
||||
|
||||
|
||||
|
|
@ -15,13 +15,15 @@
|
|||
[app.common.transit :as t]
|
||||
[app.common.types.shape-tree :as ctt]
|
||||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.data.events :as ev]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.comments :as dcmt]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.util.globals :as ug]
|
||||
[app.util.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
|
@ -32,7 +34,7 @@
|
|||
{:zoom 1
|
||||
:fullscreen? false
|
||||
:interactions-mode :show-on-click
|
||||
:interactions-show? false
|
||||
:show-interactions false
|
||||
:comments-mode :all
|
||||
:comments-show :unresolved
|
||||
:selected #{}
|
||||
|
@ -55,7 +57,7 @@
|
|||
[:page-id {:optional true} ::sm/uuid]])
|
||||
|
||||
(defn initialize
|
||||
[{:keys [file-id share-id interactions-show?] :as params}]
|
||||
[{:keys [file-id share-id] :as params}]
|
||||
(dm/assert!
|
||||
"expected valid params"
|
||||
(sm/check schema:initialize params))
|
||||
|
@ -65,13 +67,13 @@
|
|||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :current-file-id file-id)
|
||||
(assoc :current-share-id share-id)
|
||||
(update :viewer-local
|
||||
(fn [lstate]
|
||||
(if (nil? lstate)
|
||||
default-local-state
|
||||
lstate)))
|
||||
(assoc-in [:viewer-local :share-id] share-id)
|
||||
(assoc-in [:viewer-local :interactions-show?] interactions-show?)))
|
||||
(assoc-in [:viewer-local :share-id] share-id)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -256,12 +258,9 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [zoom-type (get-in state [:viewer-local :zoom-type])
|
||||
route (:route state)
|
||||
screen (-> route :data :name keyword)
|
||||
qparams (:query-params route)
|
||||
pparams (:path-params route)]
|
||||
params (rt/get-params state)]
|
||||
|
||||
(rx/of (rt/nav screen pparams (assoc qparams :zoom zoom-type)))))))
|
||||
(rx/of (rt/nav :viewer (assoc params :zoom zoom-type)))))))
|
||||
|
||||
(def increase-zoom
|
||||
(ptk/reify ::increase-zoom
|
||||
|
@ -293,14 +292,17 @@
|
|||
(ptk/reify ::zoom-to-fit
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [srect (as-> (get-in state [:route :query-params :page-id]) %
|
||||
(get-in state [:viewer :pages % :frames])
|
||||
(nth % (get-in state [:route :query-params :index]))
|
||||
(get % :selrect))
|
||||
orig-size (get-in state [:viewer-local :viewport-size])
|
||||
wdiff (/ (:width orig-size) (:width srect))
|
||||
hdiff (/ (:height orig-size) (:height srect))
|
||||
minzoom (min wdiff hdiff)]
|
||||
(let [params (rt/get-params state)
|
||||
page-id (some-> (:page-id params) uuid/parse)
|
||||
index (some-> (:index params) parse-long)
|
||||
|
||||
frames (dm/get-in state [:viewer :pages page-id :frames])
|
||||
srect (-> (nth frames index)
|
||||
(get :selrect))
|
||||
osize (dm/get-in state [:viewer-local :viewport-size])
|
||||
wdiff (/ (:width osize) (:width srect))
|
||||
hdiff (/ (:height osize) (:height srect))
|
||||
minzoom (min wdiff hdiff)]
|
||||
(-> state
|
||||
(assoc-in [:viewer-local :zoom] minzoom)
|
||||
(assoc-in [:viewer-local :zoom-type] :fit))))
|
||||
|
@ -312,17 +314,25 @@
|
|||
(ptk/reify ::zoom-to-fill
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [srect (as-> (get-in state [:route :query-params :page-id]) %
|
||||
(get-in state [:viewer :pages % :frames])
|
||||
(nth % (get-in state [:route :query-params :index]))
|
||||
(get % :selrect))
|
||||
orig-size (get-in state [:viewer-local :viewport-size])
|
||||
wdiff (/ (:width orig-size) (:width srect))
|
||||
hdiff (/ (:height orig-size) (:height srect))
|
||||
maxzoom (max wdiff hdiff)]
|
||||
|
||||
(let [params (rt/get-params state)
|
||||
page-id (some-> (:page-id params) uuid/parse)
|
||||
index (some-> (:index params) parse-long)
|
||||
|
||||
frames (dm/get-in state [:viewer :pages page-id :frames])
|
||||
srect (-> (nth frames index)
|
||||
(get :selrect))
|
||||
|
||||
osize (dm/get-in state [:viewer-local :viewport-size])
|
||||
|
||||
wdiff (/ (:width osize) (:width srect))
|
||||
hdiff (/ (:height osize) (:height srect))
|
||||
|
||||
maxzoom (max wdiff hdiff)]
|
||||
(-> state
|
||||
(assoc-in [:viewer-local :zoom] maxzoom)
|
||||
(assoc-in [:viewer-local :zoom-type] :fill))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _] (rx/of update-zoom-querystring))))
|
||||
|
||||
|
@ -376,16 +386,15 @@
|
|||
(-> state
|
||||
(dissoc :viewer-animations)
|
||||
(assoc :viewer-overlays [])))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [route (:route state)
|
||||
qparams (:query-params route)
|
||||
pparams (:path-params route)
|
||||
index (:index qparams)]
|
||||
(let [params (rt/get-params state)
|
||||
index (some-> params :index parse-long)]
|
||||
(when (pos? index)
|
||||
(rx/of
|
||||
(dcm/close-thread)
|
||||
(rt/nav :viewer pparams (assoc qparams :index (dec index)))))))))
|
||||
(dcmt/close-thread)
|
||||
(rt/nav :viewer (assoc params :index (dec index)))))))))
|
||||
|
||||
(def select-next-frame
|
||||
(ptk/reify ::select-next-frame
|
||||
|
@ -396,30 +405,25 @@
|
|||
(assoc :viewer-overlays [])))
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [route (:route state)
|
||||
pparams (:path-params route)
|
||||
qparams (:query-params route)
|
||||
|
||||
page-id (:page-id qparams)
|
||||
index (:index qparams)
|
||||
(let [params (rt/get-params state)
|
||||
index (some-> params :index parse-long)
|
||||
page-id (some-> params :page-id parse-uuid)
|
||||
|
||||
total (count (get-in state [:viewer :pages page-id :frames]))]
|
||||
|
||||
(when (< index (dec total))
|
||||
(rx/of
|
||||
(dcm/close-thread)
|
||||
(rt/nav :viewer pparams (assoc qparams :index (inc index)))))))))
|
||||
(dcmt/close-thread)
|
||||
(rt/nav :viewer params (assoc params :index (inc index)))))))))
|
||||
|
||||
(def select-first-frame
|
||||
(ptk/reify ::select-first-frame
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [route (:route state)
|
||||
qparams (:query-params route)
|
||||
pparams (:path-params route)]
|
||||
(let [params (rt/get-params state)]
|
||||
(rx/of
|
||||
(dcm/close-thread)
|
||||
(rt/nav :viewer pparams (assoc qparams :index 0)))))))
|
||||
(dcmt/close-thread)
|
||||
(rt/nav :viewer (assoc params :index 0)))))))
|
||||
|
||||
(def valid-interaction-modes
|
||||
#{:hide :show :show-on-click})
|
||||
|
@ -434,17 +438,14 @@
|
|||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:viewer-local :interactions-mode] mode)
|
||||
(assoc-in [:viewer-local :interactions-show?] (case mode
|
||||
:hide false
|
||||
:show true
|
||||
:show-on-click false))))
|
||||
(assoc-in [:viewer-local :show-interactions] (case mode
|
||||
:hide false
|
||||
:show true
|
||||
:show-on-click false))))
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [route (:route state)
|
||||
screen (-> route :data :name keyword)
|
||||
qparams (:query-params route)
|
||||
pparams (:path-params route)]
|
||||
(rx/of (rt/nav screen pparams (assoc qparams :interactions-mode mode)))))))
|
||||
(let [params (rt/get-params state)]
|
||||
(rx/of (rt/nav :viewer (assoc params :interactions-mode mode)))))))
|
||||
|
||||
(declare flash-done)
|
||||
|
||||
|
@ -453,7 +454,7 @@
|
|||
(ptk/reify ::flash-interactions
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:viewer-local :interactions-show?] true))
|
||||
(assoc-in state [:viewer-local :show-interactions] true))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
|
@ -466,7 +467,7 @@
|
|||
(ptk/reify ::flash-done
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:viewer-local :interactions-show?] false))))
|
||||
(assoc-in state [:viewer-local :show-interactions] false))))
|
||||
|
||||
(defn set-nav-scroll
|
||||
[scroll]
|
||||
|
@ -500,11 +501,8 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [route (:route state)
|
||||
screen (-> route :data :name keyword)
|
||||
qparams (:query-params route)
|
||||
pparams (:path-params route)]
|
||||
(rx/of (rt/nav screen pparams (assoc qparams :index index)))))))
|
||||
(let [params (rt/get-params state)]
|
||||
(rx/of (rt/nav :viewer (assoc params :index index)))))))
|
||||
|
||||
(defn go-to-frame
|
||||
([frame-id]
|
||||
|
@ -573,10 +571,8 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [route (:route state)
|
||||
pparams (:path-params route)
|
||||
qparams (:query-params route)]
|
||||
(rx/of (rt/nav :viewer pparams (assoc qparams :section section)))))))
|
||||
(let [params (rt/get-params state)]
|
||||
(rx/of (rt/nav :viewer (assoc params :section section)))))))
|
||||
|
||||
;; --- Overlays
|
||||
|
||||
|
@ -771,9 +767,8 @@
|
|||
(ptk/reify ::go-to-dashboard
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (get-in state [:viewer :project :team-id])
|
||||
params {:team-id team-id}]
|
||||
(rx/of (rt/nav :dashboard-projects params))))))
|
||||
(let [team-id (get-in state [:viewer :project :team-id])]
|
||||
(rx/of (dcm/go-to-dashboard-recent :team-id team-id))))))
|
||||
|
||||
(defn go-to-page
|
||||
[page-id]
|
||||
|
@ -784,13 +779,10 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [route (:route state)
|
||||
pparams (:path-params route)
|
||||
qparams (-> (:query-params route)
|
||||
(assoc :index 0)
|
||||
(assoc :page-id page-id))
|
||||
rname (get-in route [:data :name])]
|
||||
(rx/of (rt/nav rname pparams qparams))))))
|
||||
(let [params (-> (rt/get-params state)
|
||||
(assoc :index 0)
|
||||
(assoc :page-id page-id))]
|
||||
(rx/of (rt/nav :viewer params))))))
|
||||
|
||||
(defn go-to-workspace
|
||||
([] (go-to-workspace nil))
|
||||
|
@ -798,14 +790,16 @@
|
|||
(ptk/reify ::go-to-workspace
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [route (:route state)
|
||||
project-id (get-in state [:viewer :project :id])
|
||||
file-id (get-in state [:viewer :file :id])
|
||||
saved-page-id (get-in route [:query-params :page-id])
|
||||
pparams {:project-id project-id :file-id file-id}
|
||||
qparams {:page-id (or page-id saved-page-id)}]
|
||||
(rx/of (rt/nav-new-window*
|
||||
{:rname :workspace
|
||||
:path-params pparams
|
||||
:query-params qparams
|
||||
:name (str "workspace-" file-id)})))))))
|
||||
(let [params (rt/get-params state)
|
||||
file-id (get-in state [:viewer :file :id])
|
||||
team-id (get-in state [:viewer :project :team-id])
|
||||
page-id (or page-id
|
||||
(some-> (:page-id params) uuid/parse))
|
||||
params {:page-id page-id
|
||||
:file-id file-id
|
||||
:team-id team-id}
|
||||
name (dm/str "workspace-" file-id)]
|
||||
|
||||
(rx/of (rt/nav :workspace params
|
||||
::rt/new-window true
|
||||
::rt/window-name name)))))))
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.main.data.viewer.shortcuts
|
||||
(:require
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.shortcuts :as ds]
|
||||
[app.main.data.viewer :as dv]
|
||||
[app.main.store :as st]))
|
||||
|
@ -69,7 +70,7 @@
|
|||
:open-workspace {:tooltip "G W"
|
||||
:command "g w"
|
||||
:subsections [:navigation-viewer]
|
||||
:fn #(st/emit! (dv/go-to-workspace))}})
|
||||
:fn #(st/emit! (dcm/go-to-workspace))}})
|
||||
|
||||
(defn get-tooltip [shortcut]
|
||||
(assert (contains? shortcuts shortcut) (str shortcut))
|
||||
|
|
|
@ -36,16 +36,18 @@
|
|||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.comments :as dcmt]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.persistence :as dps]
|
||||
[app.main.data.plugins :as dp]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.data.project :as dpj]
|
||||
[app.main.data.workspace.bool :as dwb]
|
||||
[app.main.data.workspace.collapse :as dwco]
|
||||
[app.main.data.workspace.colors :as dwcl]
|
||||
[app.main.data.workspace.drawing :as dwd]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.fix-broken-shapes :as fbs]
|
||||
|
@ -74,6 +76,7 @@
|
|||
[app.main.features :as features]
|
||||
[app.main.features.pointer-map :as fpmap]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.streams :as ms]
|
||||
[app.main.worker :as uw]
|
||||
[app.render-wasm :as wasm]
|
||||
|
@ -81,7 +84,6 @@
|
|||
[app.util.globals :as ug]
|
||||
[app.util.http :as http]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :as storage]
|
||||
[app.util.timers :as tm]
|
||||
[app.util.webapi :as wapi]
|
||||
|
@ -100,12 +102,15 @@
|
|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(declare ^:private workspace-initialized)
|
||||
(declare ^:private fetch-libraries)
|
||||
(declare ^:private libraries-fetched)
|
||||
(declare go-to-layout)
|
||||
(declare ^:private preload-data-uris)
|
||||
|
||||
;; (declare go-to-layout)
|
||||
|
||||
;; --- Initialize Workspace
|
||||
|
||||
(defn initialize-layout
|
||||
(defn initialize-workspace-layout
|
||||
[lname]
|
||||
(ptk/reify ::initialize-layout
|
||||
ptk/UpdateEvent
|
||||
|
@ -120,104 +125,45 @@
|
|||
(rx/of (layout/ensure-layout lname))
|
||||
(rx/of (layout/ensure-layout :layers))))))
|
||||
|
||||
(defn- workspace-initialized
|
||||
[]
|
||||
(ptk/reify ::workspace-initialized
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :workspace-undo {})
|
||||
(assoc :workspace-ready? true)))
|
||||
(defn- datauri->blob-uri
|
||||
[uri]
|
||||
(->> (http/send! {:uri uri
|
||||
:response-type :blob
|
||||
:method :get})
|
||||
(rx/map :body)
|
||||
(rx/map (fn [blob] (wapi/create-uri blob)))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(rx/of
|
||||
(when (and (not (boolean (-> state :profile :props :v2-info-shown)))
|
||||
(features/active-feature? state "components/v2"))
|
||||
(modal/show :v2-info {}))
|
||||
(dp/check-open-plugin)
|
||||
(fdf/fix-deleted-fonts)
|
||||
(fbs/fix-broken-shapes)))))
|
||||
(defn- get-file-object-thumbnails
|
||||
[file-id]
|
||||
(->> (rp/cmd! :get-file-object-thumbnails {:file-id file-id})
|
||||
(rx/mapcat (fn [thumbnails]
|
||||
(->> (rx/from thumbnails)
|
||||
(rx/mapcat (fn [[k v]]
|
||||
;; we only need to fetch the thumbnail if
|
||||
;; it is a data:uri, otherwise we can just
|
||||
;; use the value as is.
|
||||
(if (str/starts-with? v "data:")
|
||||
(->> (datauri->blob-uri v)
|
||||
(rx/map (fn [uri] [k uri])))
|
||||
(rx/of [k v])))))))
|
||||
(rx/reduce conj {})))
|
||||
|
||||
(defn- workspace-data-loaded
|
||||
[data]
|
||||
(ptk/reify ::workspace-data-loaded
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [data (d/removem (comp t/pointer? val) data)]
|
||||
(assoc state :workspace-data data)))))
|
||||
|
||||
(defn- bundle-fetched
|
||||
[{:keys [features file thumbnails project team team-users comments-users]}]
|
||||
(ptk/reify ::bundle-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :users (d/index-by :id team-users))
|
||||
(assoc :workspace-thumbnails thumbnails)
|
||||
(assoc :workspace-file (dissoc file :data))
|
||||
(assoc :workspace-project project)
|
||||
(assoc :current-file-comments-users (d/index-by :id comments-users))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [team-id (:id team)
|
||||
file-id (:id file)
|
||||
stopper (rx/filter (ptk/type? ::bundle-fetched) stream)]
|
||||
|
||||
(->> (rx/concat
|
||||
;; Initialize notifications
|
||||
(rx/of (dwn/initialize team-id file-id)
|
||||
(dwsl/initialize))
|
||||
|
||||
;; Load team fonts. We must ensure custom fonts are
|
||||
;; fully loadad before mark workspace as initialized
|
||||
(rx/merge
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::df/team-fonts-loaded))
|
||||
(rx/take 1)
|
||||
(rx/ignore))
|
||||
|
||||
(rx/of (df/load-team-fonts team-id))
|
||||
|
||||
;; FIXME: move to bundle fetch stages
|
||||
|
||||
;; Load main file
|
||||
(->> (fpmap/resolve-file file)
|
||||
(rx/map :data)
|
||||
(rx/mapcat (fn [{:keys [pages-index] :as data}]
|
||||
(->> (rx/from (seq pages-index))
|
||||
(rx/mapcat
|
||||
(fn [[id page]]
|
||||
(let [page (update page :objects ctst/start-page-index)]
|
||||
(->> (uw/ask! {:cmd :initialize-page-index :page page})
|
||||
(rx/map (fn [_] [id page]))))))
|
||||
(rx/reduce conj {})
|
||||
(rx/map (fn [pages-index]
|
||||
(assoc data :pages-index pages-index))))))
|
||||
(rx/map workspace-data-loaded))
|
||||
|
||||
;; Load libraries
|
||||
(->> (rp/cmd! :get-file-libraries {:file-id file-id})
|
||||
(rx/mapcat (fn [libraries]
|
||||
(rx/merge
|
||||
(->> (rx/from libraries)
|
||||
(rx/merge-map
|
||||
(fn [{:keys [id synced-at]}]
|
||||
(->> (rp/cmd! :get-file {:id id :features features})
|
||||
(rx/map #(assoc % :synced-at synced-at)))))
|
||||
(rx/merge-map fpmap/resolve-file)
|
||||
(rx/reduce conj [])
|
||||
(rx/map libraries-fetched))
|
||||
(->> (rx/from libraries)
|
||||
(rx/map :id)
|
||||
(rx/mapcat (fn [file-id]
|
||||
(rp/cmd! :get-file-object-thumbnails {:file-id file-id :tag "component"})))
|
||||
(rx/map dwl/library-thumbnails-fetched)))))))
|
||||
|
||||
(rx/of (with-meta (workspace-initialized)
|
||||
{:file-id file-id})))
|
||||
(rx/take-until stopper))))))
|
||||
(defn- resolve-file
|
||||
[file]
|
||||
(->> (fpmap/resolve-file file)
|
||||
(rx/map :data)
|
||||
(rx/mapcat
|
||||
(fn [{:keys [pages-index] :as data}]
|
||||
(->> (rx/from (seq pages-index))
|
||||
(rx/mapcat
|
||||
(fn [[id page]]
|
||||
(let [page (update page :objects ctst/start-page-index)]
|
||||
(->> (uw/ask! {:cmd :initialize-page-index :page page})
|
||||
(rx/map (fn [_] [id page]))))))
|
||||
(rx/reduce conj {})
|
||||
(rx/map (fn [pages-index]
|
||||
(let [data (assoc data :pages-index pages-index)]
|
||||
(assoc file :data (d/removem (comp t/pointer? val) data))))))))))
|
||||
|
||||
(defn- libraries-fetched
|
||||
[libraries]
|
||||
|
@ -238,180 +184,180 @@
|
|||
(rx/concat (rx/timer 1000)
|
||||
(rx/of (dwl/notify-sync-file file-id))))))))
|
||||
|
||||
(defn- datauri->blob-uri
|
||||
[uri]
|
||||
(->> (http/send! {:uri uri
|
||||
:response-type :blob
|
||||
:method :get})
|
||||
(rx/map :body)
|
||||
(rx/map (fn [blob] (wapi/create-uri blob)))))
|
||||
|
||||
(defn- fetch-file-object-thumbnails
|
||||
(defn- fetch-libraries
|
||||
[file-id]
|
||||
(->> (rp/cmd! :get-file-object-thumbnails {:file-id file-id})
|
||||
(rx/mapcat (fn [thumbnails]
|
||||
(->> (rx/from thumbnails)
|
||||
(rx/mapcat (fn [[k v]]
|
||||
;; we only need to fetch the thumbnail if
|
||||
;; it is a data:uri, otherwise we can just
|
||||
;; use the value as is.
|
||||
(if (str/starts-with? v "data:")
|
||||
(->> (datauri->blob-uri v)
|
||||
(rx/map (fn [uri] [k uri])))
|
||||
(rx/of [k v])))))))
|
||||
(rx/reduce conj {})))
|
||||
|
||||
(defn- fetch-bundle-stage-1
|
||||
[project-id file-id]
|
||||
(ptk/reify ::fetch-bundle-stage-1
|
||||
(ptk/reify ::fetch-libries
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(->> (rp/cmd! :get-project {:id project-id})
|
||||
(rx/mapcat (fn [project]
|
||||
(rx/concat
|
||||
;; Wait the wasm module to be loaded or failed to
|
||||
;; load. We need to wait the promise to be resolved
|
||||
;; before continue with the next workspace loading
|
||||
;; steps
|
||||
(->> (rx/from wasm/module)
|
||||
(rx/ignore))
|
||||
(->> (rp/cmd! :get-team {:id (:team-id project)})
|
||||
(rx/mapcat (fn [team]
|
||||
(let [bundle {:team team
|
||||
:project project
|
||||
:file-id file-id
|
||||
:project-id project-id}]
|
||||
(rx/of (du/set-current-team team)
|
||||
(ptk/data-event ::bundle-stage-1 bundle)))))))))
|
||||
(rx/take-until
|
||||
(rx/filter (ptk/type? ::fetch-bundle) stream))))))
|
||||
(watch [_ state _]
|
||||
(let [features (features/get-team-enabled-features state)]
|
||||
(->> (rp/cmd! :get-file-libraries {:file-id file-id})
|
||||
(rx/mapcat
|
||||
(fn [libraries]
|
||||
(rx/merge
|
||||
(->> (rx/from libraries)
|
||||
(rx/merge-map
|
||||
(fn [{:keys [id synced-at]}]
|
||||
(->> (rp/cmd! :get-file {:id id :features features})
|
||||
(rx/map #(assoc % :synced-at synced-at)))))
|
||||
(rx/merge-map resolve-file)
|
||||
(rx/reduce conj [])
|
||||
(rx/map libraries-fetched))
|
||||
(->> (rx/from libraries)
|
||||
(rx/map :id)
|
||||
(rx/mapcat (fn [file-id]
|
||||
(rp/cmd! :get-file-object-thumbnails {:file-id file-id :tag "component"})))
|
||||
(rx/map dwl/library-thumbnails-fetched))))))))))
|
||||
|
||||
(defn- workspace-initialized
|
||||
[]
|
||||
(ptk/reify ::workspace-initialized
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :workspace-undo {})
|
||||
(assoc :workspace-ready true)))
|
||||
|
||||
(defn- fetch-bundle-stage-2
|
||||
[{:keys [file-id project-id] :as bundle}]
|
||||
(ptk/reify ::fetch-bundle-stage-2
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [features (features/get-team-enabled-features state)
|
||||
(watch [_ state _]
|
||||
(rx/of
|
||||
(when (and (not (boolean (-> state :profile :props :v2-info-shown)))
|
||||
(features/active-feature? state "components/v2"))
|
||||
(modal/show :v2-info {}))
|
||||
(dp/check-open-plugin)
|
||||
(fdf/fix-deleted-fonts)
|
||||
(fbs/fix-broken-shapes)))))
|
||||
|
||||
;; WTF is this?
|
||||
share-id (-> state :viewer-local :share-id)]
|
||||
(->> (rx/zip (rp/cmd! :get-file {:id file-id :features features :project-id project-id})
|
||||
(fetch-file-object-thumbnails file-id)
|
||||
(rp/cmd! :get-team-users {:file-id file-id})
|
||||
(rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id}))
|
||||
(rx/take 1)
|
||||
(rx/map (fn [[file thumbnails team-users comments-users]]
|
||||
(let [bundle (-> bundle
|
||||
(assoc :file file)
|
||||
(assoc :features features)
|
||||
(assoc :thumbnails thumbnails)
|
||||
(assoc :team-users team-users)
|
||||
(assoc :comments-users comments-users))]
|
||||
(ptk/data-event ::bundle-stage-2 bundle))))
|
||||
(rx/take-until
|
||||
(rx/filter (ptk/type? ::fetch-bundle) stream)))))))
|
||||
(defn- bundle-fetched
|
||||
[{:keys [features file thumbnails]}]
|
||||
(ptk/reify ::bundle-fetched
|
||||
IDeref
|
||||
(-deref [_]
|
||||
{:features features
|
||||
:file file
|
||||
:thumbnails thumbnails})
|
||||
|
||||
(declare go-to-component)
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :thumbnails thumbnails)
|
||||
(assoc :workspace-file (dissoc file :data))
|
||||
(assoc :workspace-data (:data file))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)
|
||||
file-id (:id file)]
|
||||
|
||||
(rx/of (dwn/initialize team-id file-id)
|
||||
(dwsl/initialize-shape-layout)
|
||||
(fetch-libraries file-id))))))
|
||||
|
||||
(defn- fetch-bundle
|
||||
"Multi-stage file bundle fetch coordinator"
|
||||
[project-id file-id]
|
||||
[file-id]
|
||||
(ptk/reify ::fetch-bundle
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rx/merge
|
||||
(rx/of (fetch-bundle-stage-1 project-id file-id))
|
||||
(let [features (features/get-team-enabled-features state)
|
||||
render-wasm? (contains? features "render-wasm/v1")
|
||||
stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream)]
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::bundle-stage-1))
|
||||
(rx/observe-on :async)
|
||||
(rx/map deref)
|
||||
(rx/map fetch-bundle-stage-2))
|
||||
(->> (rx/concat
|
||||
;; Firstly load wasm module if it is enabled and fonts
|
||||
(rx/merge
|
||||
(if ^boolean render-wasm?
|
||||
(->> (rx/from @wasm/module)
|
||||
(rx/ignore))
|
||||
(rx/empty))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::bundle-stage-2))
|
||||
(rx/observe-on :async)
|
||||
(rx/map deref)
|
||||
(rx/map bundle-fetched))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::df/fonts-loaded))
|
||||
(rx/take 1)
|
||||
(rx/ignore))
|
||||
(rx/of (df/fetch-fonts)))
|
||||
|
||||
(when-let [component-id (get-in state [:route :query-params :component-id])]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/observe-on :async)
|
||||
;; Then fetch file and thumbnails
|
||||
(->> (rx/zip (rp/cmd! :get-file {:id file-id :features features})
|
||||
(get-file-object-thumbnails file-id))
|
||||
(rx/take 1)
|
||||
(rx/map #(go-to-component (uuid/uuid component-id))))))
|
||||
(rx/mapcat
|
||||
(fn [[file thumbnails]]
|
||||
(->> (resolve-file file)
|
||||
(rx/map (fn [file]
|
||||
{:file file
|
||||
:features features
|
||||
:thumbnails thumbnails})))))
|
||||
(rx/map bundle-fetched)))
|
||||
|
||||
(rx/take-until
|
||||
(rx/filter (ptk/type? ::fetch-bundle) stream))))))
|
||||
(rx/take-until stopper-s))))))
|
||||
|
||||
(defn initialize-file
|
||||
[project-id file-id]
|
||||
(dm/assert! (uuid? project-id))
|
||||
(dm/assert! (uuid? file-id))
|
||||
(defn initialize-workspace
|
||||
[file-id]
|
||||
(assert (uuid? file-id) "expected valud uuid for `file-id`")
|
||||
|
||||
(ptk/reify ::initialize-file
|
||||
(ptk/reify ::initialize-workspace
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state
|
||||
:recent-colors (:recent-colors storage/user)
|
||||
:workspace-ready? false
|
||||
:workspace-ready false
|
||||
:current-file-id file-id
|
||||
:current-project-id project-id
|
||||
:workspace-presence {}))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(log/debug :hint "initialize-file" :file-id file-id)
|
||||
(let [stoper-s (rx/filter (ptk/type? ::finalize-file) stream)]
|
||||
(rx/merge
|
||||
(rx/of (ntf/hide)
|
||||
;; We initialize the features without knowning the
|
||||
;; team specific features in this step.
|
||||
(features/initialize)
|
||||
(dcm/retrieve-comment-threads file-id)
|
||||
(fetch-bundle project-id file-id))
|
||||
(watch [_ state stream]
|
||||
(log/debug :hint "initialize-workspace" :file-id file-id)
|
||||
(let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream)
|
||||
rparams (rt/get-params state)]
|
||||
|
||||
(->> stream
|
||||
(rx/filter dch/commit?)
|
||||
(rx/map deref)
|
||||
(rx/mapcat (fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo?]}]
|
||||
(if (and save-undo? (seq undo-changes))
|
||||
(let [entry {:undo-changes undo-changes
|
||||
:redo-changes redo-changes
|
||||
:undo-group undo-group
|
||||
:tags tags}]
|
||||
(rx/of (dwu/append-undo entry stack-undo?)))
|
||||
(rx/empty))))
|
||||
(->> (rx/merge
|
||||
(rx/of (ntf/hide)
|
||||
(dcmt/retrieve-comment-threads file-id)
|
||||
(dcmt/fetch-profiles)
|
||||
(fetch-bundle file-id))
|
||||
|
||||
(rx/take-until stoper-s)))))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::bundle-fetched))
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/mapcat (fn [{:keys [file]}]
|
||||
(rx/of (dpj/initialize-project (:project-id file))
|
||||
(-> (workspace-initialized)
|
||||
(with-meta {:file-id file-id}))))))
|
||||
|
||||
(when-let [component-id (some-> rparams :component-id parse-uuid)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/observe-on :async)
|
||||
(rx/take 1)
|
||||
(rx/map #(dwl/go-to-local-component :id component-id))))
|
||||
|
||||
(->> stream
|
||||
(rx/filter dch/commit?)
|
||||
(rx/map deref)
|
||||
(rx/mapcat (fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo?]}]
|
||||
(if (and save-undo? (seq undo-changes))
|
||||
(let [entry {:undo-changes undo-changes
|
||||
:redo-changes redo-changes
|
||||
:undo-group undo-group
|
||||
:tags tags}]
|
||||
(rx/of (dwu/append-undo entry stack-undo?)))
|
||||
(rx/empty))))))
|
||||
(rx/take-until stoper-s))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(let [name (dm/str "workspace-" file-id)]
|
||||
(unchecked-set ug/global "name" name)))))
|
||||
|
||||
(defn reload-file
|
||||
[]
|
||||
(ptk/reify ::reload-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)
|
||||
project-id (:current-project-id state)]
|
||||
(rx/of (initialize-file project-id file-id))))))
|
||||
|
||||
;; We need to inject this so there are no cycles
|
||||
(set! app.main.data.workspace.notifications/reload-file reload-file)
|
||||
(set! app.main.errors/reload-file reload-file)
|
||||
|
||||
(defn finalize-file
|
||||
[_project-id file-id]
|
||||
(defn finalize-workspace
|
||||
[file-id]
|
||||
(ptk/reify ::finalize-file
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(dissoc
|
||||
:current-file-id
|
||||
:current-project-id
|
||||
:workspace-data
|
||||
:workspace-editor-state
|
||||
:workspace-file
|
||||
|
@ -419,23 +365,37 @@
|
|||
:workspace-media-objects
|
||||
:workspace-persistence
|
||||
:workspace-presence
|
||||
:workspace-project
|
||||
:workspace-ready?
|
||||
:workspace-ready
|
||||
:workspace-undo)
|
||||
(update :workspace-global dissoc :read-only?)
|
||||
(assoc-in [:workspace-global :options-mode] :design)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dwn/finalize file-id)
|
||||
(dwsl/finalize)))))
|
||||
(watch [_ state _]
|
||||
(let [project-id (:current-project-id state)]
|
||||
|
||||
(declare go-to-page)
|
||||
(declare ^:private preload-data-uris)
|
||||
(rx/of (dwn/finalize file-id)
|
||||
(dpj/finalize-project project-id)
|
||||
(dwsl/finalize-shape-layout)
|
||||
(dwcl/stop-picker)
|
||||
(modal/hide)
|
||||
(ntf/hide))))))
|
||||
|
||||
(defn- reload-current-file
|
||||
[]
|
||||
(ptk/reify ::reload-current-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)]
|
||||
(rx/of (initialize-workspace file-id))))))
|
||||
|
||||
;; Make this event callable through dynamic resolution
|
||||
(defmethod ptk/resolve ::reload-current-file [_ _] (reload-current-file))
|
||||
|
||||
(defn initialize-page
|
||||
[page-id]
|
||||
(dm/assert! (uuid? page-id))
|
||||
(assert (uuid? page-id) "expected valid uuid for `page-id`")
|
||||
|
||||
(ptk/reify ::initialize-page
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
@ -450,30 +410,20 @@
|
|||
|
||||
;; FIXME: this should be done on `initialize-layout` (?)
|
||||
(update :workspace-layout layout/load-layout-flags)
|
||||
(update :workspace-global layout/load-layout-state)
|
||||
|
||||
(update :workspace-global assoc :background-color (-> page :options :background))
|
||||
(update-in [:route :params :query] assoc :page-id (dm/str id))))
|
||||
(update :workspace-global layout/load-layout-state)))
|
||||
|
||||
state))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
;; NOTE: there are cases between files navigation when this
|
||||
;; event is emmited but the page-index is still not loaded, so
|
||||
;; we only need to proceed when page-index is properly loaded
|
||||
(when-let [pindex (-> state :workspace-data :pages-index)]
|
||||
(if (contains? pindex page-id)
|
||||
(let [file-id (:current-file-id state)]
|
||||
(rx/of (preload-data-uris page-id)
|
||||
(dwth/watch-state-changes file-id page-id)
|
||||
(dwl/watch-component-changes)))
|
||||
(let [page-id (dm/get-in state [:workspace-data :pages 0])]
|
||||
(rx/of (go-to-page page-id))))))))
|
||||
(let [file-id (:current-file-id state)]
|
||||
(rx/of (preload-data-uris page-id)
|
||||
(dwth/watch-state-changes file-id page-id)
|
||||
(dwl/watch-component-changes))))))
|
||||
|
||||
(defn finalize-page
|
||||
[page-id]
|
||||
(dm/assert! (uuid? page-id))
|
||||
(assert (uuid? page-id) "expected valid uuid for `page-id`")
|
||||
(ptk/reify ::finalize-page
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
@ -669,7 +619,7 @@
|
|||
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(when (= id (:current-page-id state))
|
||||
go-to-file))))))
|
||||
(go-to-file)))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; WORKSPACE File Actions
|
||||
|
@ -833,7 +783,7 @@
|
|||
(let [selected (wsh/lookup-selected state)
|
||||
id (first selected)]
|
||||
(when (= (count selected) 1)
|
||||
(rx/of (go-to-layout :layers)
|
||||
(rx/of (dcm/go-to-workspace :layout :layers)
|
||||
(start-rename-shape id)))))))
|
||||
|
||||
;; --- Shape Vertical Ordering
|
||||
|
@ -1097,76 +1047,17 @@
|
|||
(rx/of (dwsh/update-shapes selected #(assoc % :proportion-lock true)))
|
||||
(rx/of (dwsh/update-shapes selected #(update % :proportion-lock not))))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Navigation
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn workspace-focus-lost
|
||||
[]
|
||||
(ptk/reify ::workspace-focus-lost
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
;; FIXME: remove the `?` from show-distances?
|
||||
(assoc-in state [:workspace-global :show-distances?] false))))
|
||||
|
||||
(defn navigate-to-project
|
||||
[project-id]
|
||||
(ptk/reify ::navigate-to-project
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [page-ids (get-in state [:projects project-id :pages])
|
||||
params {:project project-id :page (first page-ids)}]
|
||||
(rx/of (rt/nav :workspace/page params))))))
|
||||
|
||||
(defn go-to-page
|
||||
([]
|
||||
(ptk/reify ::go-to-page
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [project-id (:current-project-id state)
|
||||
file-id (:current-file-id state)
|
||||
page-id (get-in state [:workspace-data :pages 0])
|
||||
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/of (rt/nav' :workspace pparams qparams))))))
|
||||
([page-id]
|
||||
(dm/assert! (uuid? page-id))
|
||||
(ptk/reify ::go-to-page-2
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [project-id (:current-project-id state)
|
||||
file-id (:current-file-id state)
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/of (rt/nav :workspace pparams qparams)))))))
|
||||
|
||||
(defn go-to-layout
|
||||
[layout]
|
||||
(ptk/reify ::go-to-layout
|
||||
IDeref
|
||||
(-deref [_] {:layout layout})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [project-id (get-in state [:workspace-project :id])
|
||||
file-id (get-in state [:workspace-file :id])
|
||||
page-id (get state :current-page-id)
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id :layout (name layout)}]
|
||||
(rx/of (rt/nav :workspace pparams qparams))))))
|
||||
|
||||
(defn navigate-to-library
|
||||
"Open a new tab, and navigate to the workspace with the provided file"
|
||||
[library-id]
|
||||
(ptk/reify ::navigate-to-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when-let [file (dm/get-in state [:workspace-libraries library-id])]
|
||||
(let [params {:rname :workspace
|
||||
:path-params {:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
:query-params {:page-id (dm/get-in file [:data :pages 0])}}]
|
||||
(rx/of (rt/nav-new-window* params)))))))
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Navigation
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn set-assets-section-open
|
||||
[file-id section open?]
|
||||
|
@ -1228,110 +1119,18 @@
|
|||
(update-in state [:workspace-assets :selected] dissoc file-id)
|
||||
(update state :workspace-assets dissoc :selected))))))
|
||||
|
||||
(defn go-to-main-instance
|
||||
[file-id component-id]
|
||||
(dm/assert!
|
||||
"expected uuid type for `file-id` parameter (nilable)"
|
||||
(or (nil? file-id)
|
||||
(uuid? file-id)))
|
||||
|
||||
(dm/assert!
|
||||
"expected uuid type for `component-id` parameter"
|
||||
(uuid? component-id))
|
||||
|
||||
(ptk/reify ::go-to-main-instance
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [current-file-id (:current-file-id state)
|
||||
current-page-id (:current-page-id state)
|
||||
current-project-id (:current-project-id state)
|
||||
file-id (or file-id current-file-id)
|
||||
|
||||
select-and-zoom
|
||||
(fn [shape-id]
|
||||
(rx/of (dws/select-shapes (d/ordered-set shape-id))
|
||||
dwz/zoom-to-selected-shape))
|
||||
|
||||
redirect-to-page
|
||||
(fn [page-id shape-id]
|
||||
(rx/concat
|
||||
(rx/of (go-to-page page-id))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::initialize-page))
|
||||
(rx/take 1)
|
||||
(rx/observe-on :async))
|
||||
(select-and-zoom shape-id)))
|
||||
|
||||
redirect-to-file
|
||||
(fn [file-id page-id]
|
||||
(let [pparams {:file-id file-id :project-id current-project-id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/merge
|
||||
(rx/of (rt/nav :workspace pparams qparams))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/map meta)
|
||||
(rx/filter #(= file-id (:file-id %)))
|
||||
(rx/take 1)
|
||||
(rx/observe-on :async)
|
||||
(rx/map #(go-to-main-instance file-id component-id))))))]
|
||||
|
||||
(if (= file-id current-file-id)
|
||||
(let [component (dm/get-in state [:workspace-data :components component-id])
|
||||
page-id (:main-instance-page component)
|
||||
shape-id (:main-instance-id component)]
|
||||
(when (some? page-id)
|
||||
(if (= page-id current-page-id)
|
||||
(select-and-zoom shape-id)
|
||||
(redirect-to-page page-id shape-id))))
|
||||
|
||||
(let [component (dm/get-in state [:workspace-libraries file-id :data :components component-id])]
|
||||
(some->> (:main-instance-page component)
|
||||
(redirect-to-file file-id))))))))
|
||||
|
||||
(defn go-to-component
|
||||
[component-id]
|
||||
(ptk/reify ::go-to-component
|
||||
IDeref
|
||||
(-deref [_] {:layout :assets})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [components-v2 (features/active-feature? state "components/v2")]
|
||||
(if components-v2
|
||||
(rx/of (go-to-main-instance nil component-id))
|
||||
(let [project-id (get-in state [:workspace-project :id])
|
||||
file-id (get-in state [:workspace-file :id])
|
||||
page-id (get state :current-page-id)
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id :layout :assets}]
|
||||
(rx/of (rt/nav :workspace pparams qparams)
|
||||
(set-assets-section-open file-id :library true)
|
||||
(set-assets-section-open file-id :components true)
|
||||
(select-single-asset file-id component-id :components))))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [components-v2 (features/active-feature? state "components/v2")
|
||||
wrapper-id (str "component-shape-id-" component-id)]
|
||||
(when-not components-v2
|
||||
(tm/schedule-on-idle #(dom/scroll-into-view-if-needed! (dom/get-element wrapper-id))))))))
|
||||
|
||||
(defn show-component-in-assets
|
||||
[component-id]
|
||||
|
||||
(ptk/reify ::show-component-in-assets
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [project-id (get-in state [:workspace-project :id])
|
||||
file-id (get-in state [:workspace-file :id])
|
||||
page-id (get state :current-page-id)
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id page-id :layout :assets}
|
||||
component-path (cfh/split-path (get-in state [:workspace-data :components component-id :path]))
|
||||
paths (map (fn [i] (cfh/join-path (take (inc i) component-path))) (range (count component-path)))]
|
||||
(let [component-path (cfh/split-path (get-in state [:workspace-data :components component-id :path]))
|
||||
paths (map (fn [i] (cfh/join-path (take (inc i) component-path))) (range (count component-path)))
|
||||
file-id (:current-file-id state)]
|
||||
(rx/concat
|
||||
(rx/from (map #(set-assets-group-open file-id :components % true) paths))
|
||||
(rx/of (rt/nav :workspace pparams qparams)
|
||||
(rx/of (dcm/go-to-workspace :layout :assets)
|
||||
(set-assets-section-open file-id :library true)
|
||||
(set-assets-section-open file-id :components true)
|
||||
(select-single-asset file-id component-id :components)))))
|
||||
|
@ -1341,55 +1140,6 @@
|
|||
(let [wrapper-id (str "component-shape-id-" component-id)]
|
||||
(tm/schedule-on-idle #(dom/scroll-into-view-if-needed! (dom/get-element wrapper-id)))))))
|
||||
|
||||
(def go-to-file
|
||||
(ptk/reify ::go-to-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [{:keys [id project-id data] :as file} (:workspace-file state)
|
||||
page-id (get-in data [:pages 0])
|
||||
pparams {:project-id project-id :file-id id}
|
||||
qparams {:page-id page-id}]
|
||||
(rx/of (rt/nav :workspace pparams qparams))))))
|
||||
|
||||
(defn go-to-viewer
|
||||
([] (go-to-viewer {}))
|
||||
([{:keys [file-id page-id section frame-id]}]
|
||||
(ptk/reify ::go-to-viewer
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [{:keys [current-file-id current-page-id]} state
|
||||
pparams {:file-id (or file-id current-file-id)}
|
||||
qparams (cond-> {:page-id (or page-id current-page-id)}
|
||||
(some? section)
|
||||
(assoc :section section)
|
||||
(some? frame-id)
|
||||
(assoc :frame-id frame-id))]
|
||||
(rx/of ::dps/force-persist
|
||||
(rt/nav-new-window* {:rname :viewer
|
||||
:path-params pparams
|
||||
:query-params qparams
|
||||
:name (str "viewer-" (:file-id pparams))})))))))
|
||||
|
||||
(defn go-to-dashboard
|
||||
([] (go-to-dashboard nil))
|
||||
([{:keys [team-id]}]
|
||||
(ptk/reify ::go-to-dashboard
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when-let [team-id (or team-id (:current-team-id state))]
|
||||
(rx/of ::dps/force-persist
|
||||
(rt/nav :dashboard-projects {:team-id team-id})))))))
|
||||
|
||||
(defn go-to-dashboard-fonts
|
||||
[]
|
||||
(ptk/reify ::go-to-dashboard-fonts
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [team-id (:current-team-id state)]
|
||||
(rx/of ::dps/force-persist
|
||||
(rt/nav :dashboard-fonts {:team-id team-id}))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Context Menu
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1625,8 +1375,8 @@
|
|||
(rx/catch on-copy-error)
|
||||
(rx/ignore)))
|
||||
|
||||
;; FIXME: this is to support Firefox versions below 116 that don't support `ClipboardItem`
|
||||
;; after the version 116 is less common we could remove this.
|
||||
;; FIXME: this is to support Firefox versions below 116 that don't support
|
||||
;; `ClipboardItem` after the version 116 is less common we could remove this.
|
||||
;; https://caniuse.com/?search=ClipboardItem
|
||||
(->> (rx/from shapes)
|
||||
(rx/merge-map (partial prepare-object objects frame-id))
|
||||
|
@ -1909,7 +1659,8 @@
|
|||
;; the pasted object doesn't fit we try to:
|
||||
;;
|
||||
;; - Align it to the limits on the x and y axis
|
||||
;; - Respect the distance of the object to the right and bottom in the original frame
|
||||
;; - Respect the distance of the object to the right
|
||||
;; and bottom in the original frame
|
||||
(gpt/point paste-x paste-y))]
|
||||
[frame-id delta (dec (count (:shapes selected-frame-obj)))]))
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
[app.common.types.shape :refer [check-stroke!]]
|
||||
[app.common.types.shape.shadow :refer [check-shadow!]]
|
||||
[app.main.broadcast :as mbc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as md]
|
||||
[app.main.data.workspace.layout :as layout]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
[app.common.schema :as sm]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.comments :as dcmt]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.common :as dwco]
|
||||
[app.main.data.workspace.drawing :as dwd]
|
||||
|
@ -23,7 +24,6 @@
|
|||
[app.main.repo :as rp]
|
||||
[app.main.streams :as ms]
|
||||
[app.util.mouse :as mse]
|
||||
[app.util.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
|
@ -38,7 +38,7 @@
|
|||
(watch [_ _ stream]
|
||||
(let [stopper (rx/filter #(= ::finalize %) stream)]
|
||||
(rx/merge
|
||||
(rx/of (dcm/retrieve-comment-threads file-id))
|
||||
(rx/of (dcmt/retrieve-comment-threads file-id))
|
||||
(->> stream
|
||||
(rx/filter mse/mouse-event?)
|
||||
(rx/filter mse/mouse-click-event?)
|
||||
|
@ -60,8 +60,8 @@
|
|||
(watch [_ state _]
|
||||
(let [local (:comments-local state)]
|
||||
(cond
|
||||
(:draft local) (rx/of (dcm/close-thread))
|
||||
(:open local) (rx/of (dcm/close-thread))
|
||||
(:draft local) (rx/of (dcmt/close-thread))
|
||||
(:open local) (rx/of (dcmt/close-thread))
|
||||
|
||||
:else
|
||||
(rx/of (dw/clear-edition-mode)
|
||||
|
@ -78,19 +78,19 @@
|
|||
(watch [_ state _]
|
||||
(let [local (:comments-local state)]
|
||||
(if (some? (:open local))
|
||||
(rx/of (dcm/close-thread))
|
||||
(rx/of (dcmt/close-thread))
|
||||
(let [page-id (:current-page-id state)
|
||||
file-id (:current-file-id state)
|
||||
params {:position position
|
||||
:page-id page-id
|
||||
:file-id file-id}]
|
||||
(rx/of (dcm/create-draft params))))))))
|
||||
(rx/of (dcmt/create-draft params))))))))
|
||||
|
||||
(defn center-to-comment-thread
|
||||
[{:keys [position] :as thread}]
|
||||
(dm/assert!
|
||||
"expected valid comment thread"
|
||||
(dcm/check-comment-thread! thread))
|
||||
(dcmt/check-comment-thread! thread))
|
||||
|
||||
(ptk/reify ::center-to-comment-thread
|
||||
ptk/UpdateEvent
|
||||
|
@ -109,22 +109,21 @@
|
|||
[thread]
|
||||
(dm/assert!
|
||||
"expected valid comment thread"
|
||||
(dcm/check-comment-thread! thread))
|
||||
(dcmt/check-comment-thread! thread))
|
||||
(ptk/reify ::open-comment-thread
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [pparams {:project-id (:project-id thread)
|
||||
:file-id (:file-id thread)}
|
||||
qparams {:page-id (:page-id thread)}]
|
||||
(rx/merge
|
||||
(rx/of (rt/nav :workspace pparams qparams))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dwv/initialize-viewport))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rx/of (center-to-comment-thread thread)
|
||||
(dwd/select-for-drawing :comments)
|
||||
(with-meta (dcm/open-thread thread)
|
||||
{::ev/origin "workspace"})))))))))
|
||||
(rx/merge
|
||||
(rx/of (dcm/go-to-workspace :file-id (:file-id thread)
|
||||
:page-id (:page-id thread)))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dwv/initialize-viewport))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rx/of (center-to-comment-thread thread)
|
||||
(dwd/select-for-drawing :comments)
|
||||
(with-meta (dcmt/open-thread thread)
|
||||
{::ev/origin "workspace"}))))))))
|
||||
|
||||
(defn update-comment-thread-position
|
||||
([thread [new-x new-y]]
|
||||
|
@ -133,7 +132,7 @@
|
|||
([thread [new-x new-y] frame-id]
|
||||
(dm/assert!
|
||||
"expected valid comment thread"
|
||||
(dcm/check-comment-thread! thread))
|
||||
(dcmt/check-comment-thread! thread))
|
||||
(ptk/reify ::update-comment-thread-position
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.types.page :as ctp]
|
||||
[app.main.data.changes :as dwc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
[app.common.types.shape.interactions :as ctsi]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.util.storage :as storage]
|
||||
[clojure.set :as set]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
[app.config :as cf]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.comments :as dc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.workspace :as-alias dw]
|
||||
|
@ -40,14 +41,15 @@
|
|||
[app.main.data.workspace.thumbnails :as dwt]
|
||||
[app.main.data.workspace.transforms :as dwtr]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.data.workspace.zoom :as dwz]
|
||||
[app.main.features :as features]
|
||||
[app.main.features.pointer-map :as fpmap]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.util.color :as uc]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :as storage]
|
||||
[app.util.time :as dt]
|
||||
[beicon.v2.core :as rx]
|
||||
|
@ -684,21 +686,49 @@
|
|||
(rx/of (when can-detach?
|
||||
(dch/commit-changes changes)))))))
|
||||
|
||||
(defn nav-to-component-file
|
||||
(defn go-to-component-file
|
||||
[file-id component]
|
||||
(dm/assert! (uuid? file-id))
|
||||
(dm/assert! (some? component))
|
||||
(ptk/reify ::nav-to-component-file
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [project-id (get-in state [:workspace-libraries file-id :project-id])
|
||||
path-params {:project-id project-id
|
||||
:file-id file-id}
|
||||
query-params {:page-id (:main-instance-page component)
|
||||
:component-id (:id component)}]
|
||||
(rx/of (rt/nav-new-window* {:rname :workspace
|
||||
:path-params path-params
|
||||
:query-params query-params}))))))
|
||||
(let [params (-> (rt/get-params state)
|
||||
(assoc :file-id file-id)
|
||||
(assoc :page-id (:main-instance-page component))
|
||||
(assoc :component-id (:id component)))]
|
||||
(rx/of (rt/nav :workspace params :new-window? true))))))
|
||||
|
||||
|
||||
(defn go-to-local-component
|
||||
[& {:keys [id] :as options}]
|
||||
(ptk/reify ::go-to-local-component
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [current-page-id (:current-page-id state)
|
||||
|
||||
select-and-zoom
|
||||
(fn [shape-id]
|
||||
(rx/of (dws/select-shapes (d/ordered-set shape-id))
|
||||
dwz/zoom-to-selected-shape))
|
||||
|
||||
redirect-to-page
|
||||
(fn [page-id shape-id]
|
||||
(rx/merge
|
||||
(rx/of (dcm/go-to-workspace :page-id page-id))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::initialize-page))
|
||||
(rx/take 1)
|
||||
(rx/observe-on :async)
|
||||
(rx/mapcat (fn [_] (select-and-zoom shape-id))))))]
|
||||
|
||||
(when-let [component (dm/get-in state [:workspace-data :components id])]
|
||||
(let [page-id (:main-instance-page component)
|
||||
shape-id (:main-instance-id component)]
|
||||
(when (some? page-id)
|
||||
(if (= page-id current-page-id)
|
||||
(select-and-zoom shape-id)
|
||||
(redirect-to-page page-id shape-id)))))))))
|
||||
|
||||
(defn library-thumbnails-fetched
|
||||
[thumbnails]
|
||||
|
@ -1117,9 +1147,11 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(rp/cmd! :ignore-file-library-sync-status
|
||||
{:file-id (get-in state [:workspace-file :id])
|
||||
:date (dt/now)}))))
|
||||
(let [file-id (:current-file-id state)]
|
||||
(->> (rp/cmd! :ignore-file-library-sync-status
|
||||
{:file-id file-id
|
||||
:date (dt/now)})
|
||||
(rx/ignore))))))
|
||||
|
||||
(defn assets-need-sync
|
||||
"Get a lazy sequence of all the assets of each type in the library that have
|
||||
|
@ -1309,23 +1341,6 @@
|
|||
(->> (rp/cmd! :set-file-shared params)
|
||||
(rx/ignore))))))
|
||||
|
||||
(defn- shared-files-fetched
|
||||
[files]
|
||||
(ptk/reify ::shared-files-fetched
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [state (dissoc state :files)]
|
||||
(assoc state :workspace-shared-files files)))))
|
||||
|
||||
(defn fetch-shared-files
|
||||
[{:keys [team-id] :as params}]
|
||||
(dm/assert! (uuid? team-id))
|
||||
(ptk/reify ::fetch-shared-files
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :get-team-shared-files {:team-id team-id})
|
||||
(rx/map shared-files-fetched)))))
|
||||
|
||||
;; --- Link and unlink Files
|
||||
|
||||
(defn link-file-to-library
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[app.main.data.modal :as modal]
|
||||
[app.main.data.plugins :as dpl]
|
||||
[app.main.data.websocket :as dws]
|
||||
[app.main.data.workspace :as-alias dw]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.layout :as dwly]
|
||||
|
@ -30,9 +31,6 @@
|
|||
[clojure.set :as set]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
;; From app.main.data.workspace we can use directly because it causes a circular dependency
|
||||
(def reload-file nil)
|
||||
|
||||
;; FIXME: this ns should be renamed to something different
|
||||
|
||||
(declare process-message)
|
||||
|
@ -292,7 +290,7 @@
|
|||
curr-vern (dm/get-in state [:workspace-file :vern])
|
||||
reload? (and (= file-id curr-file-id) (not= vern curr-vern))]
|
||||
(when reload?
|
||||
(rx/of (reload-file)))))))
|
||||
(rx/of (ptk/event ::dw/reload-current-file)))))))
|
||||
|
||||
(def ^:private schema:handle-library-change
|
||||
[:map {:title "handle-library-change"}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
[app.common.types.component :as ctk]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as md]
|
||||
[app.main.data.workspace.collapse :as dwc]
|
||||
[app.main.data.workspace.specialized-panel :as-alias dwsp]
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.workspace.colors :as cl]
|
||||
[app.main.data.workspace.grid-layout.editor :as dwge]
|
||||
[app.main.data.workspace.modifiers :as dwm]
|
||||
|
@ -110,13 +110,15 @@
|
|||
:undo-group undo-group})))
|
||||
(rx/empty))))))
|
||||
|
||||
(defn initialize
|
||||
(defn initialize-shape-layout
|
||||
[]
|
||||
(ptk/reify ::initialize
|
||||
(ptk/reify ::initialize-shape-layout
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [stopper (rx/filter (ptk/type? ::finalize) stream)]
|
||||
(let [stopper (rx/filter (ptk/type? ::finalize-shape-layout) stream)]
|
||||
(->> stream
|
||||
;; FIXME: we don't need use types for simple signaling,
|
||||
;; we can just use a keyword for it
|
||||
(rx/filter (ptk/type? :layout/update))
|
||||
(rx/map deref)
|
||||
;; We buffer the updates to the layout so if there are many changes at the same time
|
||||
|
@ -129,9 +131,9 @@
|
|||
(update-layout-positions {:ids ids}))))
|
||||
(rx/take-until stopper))))))
|
||||
|
||||
(defn finalize
|
||||
(defn finalize-shape-layout
|
||||
[]
|
||||
(ptk/reify ::finalize))
|
||||
(ptk/data-event ::finalize-shape-layout))
|
||||
|
||||
(defn create-layout-from-id
|
||||
[id type & {:keys [from-frame? calculate-params?] :or {from-frame? false calculate-params? true}}]
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
[app.common.types.shape-tree :as ctst]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.comments :as dc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
(ns app.main.data.workspace.shortcuts
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.exports.assets :as de]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.plugins :as dpl]
|
||||
[app.main.data.preview :as dp]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.data.shortcuts :as ds]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.colors :as mdc]
|
||||
[app.main.data.workspace.drawing :as dwd]
|
||||
|
@ -440,17 +441,18 @@
|
|||
:toggle-layers {:tooltip (ds/alt "L")
|
||||
:command (ds/a-mod "l")
|
||||
:subsections [:panels]
|
||||
:fn #(st/emit! (dw/go-to-layout :layers))}
|
||||
:fn #(st/emit! (dcm/go-to-workspace :layout :layers))}
|
||||
|
||||
:toggle-assets {:tooltip (ds/alt "I")
|
||||
:command (ds/a-mod "i")
|
||||
:subsections [:panels]
|
||||
:fn #(st/emit! (dw/go-to-layout :assets))}
|
||||
:fn #(st/emit! (dcm/go-to-workspace :layout :assets))}
|
||||
|
||||
:toggle-history {:tooltip (ds/alt "H")
|
||||
:command (ds/a-mod "h")
|
||||
:subsections [:panels]
|
||||
:fn #(emit-when-no-readonly (dw/go-to-layout :document-history))}
|
||||
:fn #(emit-when-no-readonly
|
||||
(dcm/go-to-workspace :layout :document-history))}
|
||||
|
||||
:toggle-colorpalette {:tooltip (ds/alt "P")
|
||||
:command (ds/a-mod "p")
|
||||
|
@ -516,22 +518,22 @@
|
|||
:open-viewer {:tooltip "G V"
|
||||
:command "g v"
|
||||
:subsections [:navigation-workspace]
|
||||
:fn #(st/emit! (dw/go-to-viewer))}
|
||||
:fn #(st/emit! (dcm/go-to-viewer))}
|
||||
|
||||
:open-inspect {:tooltip "G I"
|
||||
:command "g i"
|
||||
:subsections [:navigation-workspace]
|
||||
:fn #(st/emit! (dw/go-to-viewer {:section :inspect}))}
|
||||
:fn #(st/emit! (dcm/go-to-viewer :section :inspect))}
|
||||
|
||||
:open-comments {:tooltip "G C"
|
||||
:command "g c"
|
||||
:subsections [:navigation-workspace]
|
||||
:fn #(st/emit! (dw/go-to-viewer {:section :comments}))}
|
||||
:fn #(st/emit! (dcm/go-to-viewer :section :comments))}
|
||||
|
||||
:open-dashboard {:tooltip "G D"
|
||||
:command "g d"
|
||||
:subsections [:navigation-workspace]
|
||||
:fn #(st/emit! (dw/go-to-dashboard))}
|
||||
:fn #(st/emit! (dcm/go-to-dashboard-recent))}
|
||||
|
||||
:select-prev {:tooltip (ds/shift "tab")
|
||||
:command "shift+tab"
|
||||
|
|
|
@ -27,10 +27,6 @@
|
|||
(-> (lookup-page state page-id)
|
||||
(get :objects))))
|
||||
|
||||
(defn lookup-viewer-objects
|
||||
([state page-id]
|
||||
(dm/get-in state [:viewer :pages page-id :objects])))
|
||||
|
||||
(defn lookup-library-objects
|
||||
[state file-id page-id]
|
||||
(dm/get-in state [:workspace-libraries file-id :data :pages-index page-id :objects]))
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
[app.common.text :as txt]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.modifiers :as dwm]
|
||||
|
@ -27,7 +27,7 @@
|
|||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.features :as features]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.util.router :as rt]
|
||||
[app.main.router :as rt]
|
||||
[app.util.text-editor :as ted]
|
||||
[app.util.text.content.styles :as styles]
|
||||
[app.util.timers :as ts]
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
[app.common.schema :as sm]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.util.router :as rt]
|
||||
[app.util.time :as dt]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
@ -290,14 +290,8 @@
|
|||
(ptk/reify ::assure-valid-current-page
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [current_page (:current-page-id state)
|
||||
pages (get-in state [:workspace-data :pages])
|
||||
exists? (some #(= current_page %) pages)
|
||||
|
||||
project-id (:current-project-id state)
|
||||
file-id (:current-file-id state)
|
||||
pparams {:file-id file-id :project-id project-id}
|
||||
qparams {:page-id (first pages)}]
|
||||
(if exists?
|
||||
(let [page-id (:current-page-id state)
|
||||
pages (dm/get-in state [:workspace-data :pages])]
|
||||
(if (contains? pages page-id)
|
||||
(rx/empty)
|
||||
(rx/of (rt/nav :workspace pparams qparams)))))))
|
||||
(rx/of (dcm/go-to-workspace :page-id (first pages))))))))
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.events :as ev]
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.persistence :as dwp]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
|
@ -25,7 +26,7 @@
|
|||
(declare fetch-versions)
|
||||
|
||||
(defn init-version-state
|
||||
[file-id]
|
||||
[]
|
||||
(ptk/reify ::init-version-state
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
|
@ -33,7 +34,7 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (fetch-versions file-id)))))
|
||||
(rx/of (fetch-versions)))))
|
||||
|
||||
(defn update-version-state
|
||||
[version-state]
|
||||
|
@ -43,123 +44,90 @@
|
|||
(update state :workspace-versions merge version-state))))
|
||||
|
||||
(defn fetch-versions
|
||||
[file-id]
|
||||
(dm/assert! (uuid? file-id))
|
||||
[]
|
||||
(ptk/reify ::fetch-versions
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :get-file-snapshots {:file-id file-id})
|
||||
(rx/map #(update-version-state {:status :loaded :data %}))))))
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)]
|
||||
(->> (rp/cmd! :get-file-snapshots {:file-id file-id})
|
||||
(rx/map #(update-version-state {:status :loaded :data %})))))))
|
||||
|
||||
(defn create-version
|
||||
[file-id]
|
||||
(dm/assert! (uuid? file-id))
|
||||
[]
|
||||
(ptk/reify ::create-version
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [label (dt/format (dt/now) :date-full)]
|
||||
(watch [_ state _]
|
||||
(let [label (dt/format (dt/now) :date-full)
|
||||
file-id (:current-file-id state)]
|
||||
|
||||
;; Force persist before creating snapshot, otherwise we could loss changes
|
||||
(rx/concat
|
||||
(rx/of ::dwp/force-persist)
|
||||
(rx/of ::dwp/force-persist
|
||||
(ptk/event ::ev/event {::ev/name "create-version"}))
|
||||
|
||||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :create-file-snapshot {:file-id file-id :label label}))
|
||||
(rx/mapcat
|
||||
(fn [{:keys [id]}]
|
||||
(rx/of
|
||||
(update-version-state {:editing id})
|
||||
(fetch-versions file-id)))))
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "create-version"})))))))
|
||||
|
||||
(defn create-version-from-plugins
|
||||
[file-id label resolve reject]
|
||||
(dm/assert! (uuid? file-id))
|
||||
(ptk/reify ::create-version-plugins
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
;; Force persist before creating snapshot, otherwise we could loss changes
|
||||
(->> (rx/concat
|
||||
(rx/of ::dwp/force-persist)
|
||||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :create-file-snapshot {:file-id file-id :label label}))
|
||||
|
||||
(rx/mapcat
|
||||
(fn [{:keys [id]}]
|
||||
(->> (rp/cmd! :get-file-snapshots {:file-id file-id})
|
||||
(rx/take 1)
|
||||
(rx/map (fn [versions] (d/seek #(= id (:id %)) versions))))))
|
||||
(rx/tap resolve)
|
||||
(rx/ignore))
|
||||
(rx/of (ptk/event ::ev/event {::ev/origin "plugins"
|
||||
::ev/name "create-version"})))
|
||||
|
||||
;; On error reject the promise and empty the stream
|
||||
(rx/catch (fn [error]
|
||||
(reject error)
|
||||
(rx/empty)))))))
|
||||
(rx/of (update-version-state {:editing id})
|
||||
(fetch-versions))))))))))
|
||||
|
||||
(defn rename-version
|
||||
[file-id id label]
|
||||
(dm/assert! (uuid? file-id))
|
||||
(dm/assert! (uuid? id))
|
||||
(dm/assert! (and (string? label) (d/not-empty? label)))
|
||||
[id label]
|
||||
(assert (uuid? id) "expected valid uuid for `id`")
|
||||
(assert (sm/valid-text? label) "expected not empty string for `label`")
|
||||
|
||||
(ptk/reify ::rename-version
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/merge
|
||||
(rx/of (update-version-state {:editing false}))
|
||||
(->> (rp/cmd! :update-file-snapshot {:id id :label label})
|
||||
(rx/map #(fetch-versions file-id)))
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "rename-version"}))))))
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)]
|
||||
(rx/merge
|
||||
(rx/of (update-version-state {:editing false})
|
||||
(ptk/event ::ev/event {::ev/name "rename-version"
|
||||
:file-id file-id}))
|
||||
(->> (rp/cmd! :update-file-snapshot {:id id :label label})
|
||||
(rx/map fetch-versions)))))))
|
||||
|
||||
(defn restore-version
|
||||
[project-id file-id id origin]
|
||||
(dm/assert! (uuid? project-id))
|
||||
(dm/assert! (uuid? file-id))
|
||||
(dm/assert! (uuid? id))
|
||||
[id origin]
|
||||
(assert (uuid? id) "expected valid uuid for `id`")
|
||||
|
||||
(ptk/reify ::restore-version
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/concat
|
||||
(rx/of ::dwp/force-persist)
|
||||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
|
||||
(rx/map #(dw/initialize-file project-id file-id)))
|
||||
(case origin
|
||||
:version
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "restore-pin-version"}))
|
||||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)]
|
||||
(rx/concat
|
||||
(rx/of ::dwp/force-persist)
|
||||
|
||||
:snapshot
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "restore-autosave"}))
|
||||
;; FIXME: we should abstract this
|
||||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
|
||||
(rx/map #(dw/initialize-workspace file-id)))
|
||||
|
||||
:plugin
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"}))
|
||||
|
||||
(rx/empty))))))
|
||||
(when-let [name (case origin
|
||||
:version "restore-pin-version"
|
||||
:snapshot "restore-autosave"
|
||||
:plugin "restore-version-plugin"
|
||||
nil)]
|
||||
(rx/of (ptk/event ::ev/event {::ev/name name}))))))))
|
||||
|
||||
(defn delete-version
|
||||
[file-id id]
|
||||
(dm/assert! (uuid? file-id))
|
||||
(dm/assert! (uuid? id))
|
||||
[id]
|
||||
(assert (uuid? id) "expected valid uuid for `id`")
|
||||
|
||||
(ptk/reify ::delete-version
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :delete-file-snapshot {:id id})
|
||||
(rx/map #(fetch-versions file-id))))))
|
||||
(rx/map fetch-versions)))))
|
||||
|
||||
(defn pin-version
|
||||
[file-id id]
|
||||
(dm/assert! (uuid? file-id))
|
||||
(dm/assert! (uuid? id))
|
||||
|
||||
[id]
|
||||
(assert (uuid? id) "expected valid uuid for `id`")
|
||||
(ptk/reify ::pin-version
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -168,8 +136,82 @@
|
|||
params {:id id
|
||||
:label (dt/format (:created-at version) :date-full)}]
|
||||
|
||||
(rx/concat
|
||||
(->> (rp/cmd! :update-file-snapshot params)
|
||||
(rx/mapcat #(rx/of (update-version-state {:editing id})
|
||||
(fetch-versions file-id))))
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "pin-version"})))))))
|
||||
(->> (rp/cmd! :update-file-snapshot params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (update-version-state {:editing id})
|
||||
(fetch-versions)
|
||||
(ptk/event ::ev/event {::ev/name "pin-version"})))))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; PLUGINS SPECIFIC EVENTS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- wait-persisted-status
|
||||
[]
|
||||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)))
|
||||
|
||||
(defn create-version-from-plugins
|
||||
[file-id label resolve reject]
|
||||
|
||||
(assert (uuid? file-id) "expected valid uuid for `file-id`")
|
||||
(assert (sm/valid-text? label) "expected not empty string for `label`")
|
||||
|
||||
(ptk/reify ::create-version-from-plugins
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [current-file-id (:current-file-id state)]
|
||||
;; Force persist before creating snapshot, otherwise we could loss changes
|
||||
(->> (rx/concat
|
||||
(rx/of (ptk/event ::ev/event {::ev/origin "plugins"
|
||||
::ev/name "create-version"}))
|
||||
|
||||
(when (= file-id current-file-id)
|
||||
(rx/of ::dwp/force-persist))
|
||||
|
||||
(->> (if (= file-id current-file-id)
|
||||
(wait-persisted-status)
|
||||
(rx/of :nothing))
|
||||
(rx/mapcat
|
||||
(fn [_]
|
||||
(rp/cmd! :create-file-snapshot {:file-id file-id :label label})))
|
||||
(rx/mapcat
|
||||
(fn [{:keys [id]}]
|
||||
(->> (rp/cmd! :get-file-snapshots {:file-id file-id})
|
||||
(rx/take 1)
|
||||
(rx/map (fn [versions] (d/seek #(= id (:id %)) versions))))))
|
||||
(rx/tap resolve)
|
||||
(rx/ignore)))
|
||||
|
||||
;; On error reject the promise and empty the stream
|
||||
(rx/catch (fn [error]
|
||||
(reject error)
|
||||
(rx/empty))))))))
|
||||
|
||||
(defn restore-version-from-plugin
|
||||
[file-id id resolve _reject]
|
||||
(assert (uuid? id) "expected valid uuid for `id`")
|
||||
|
||||
(ptk/reify ::restore-version-from-plugins
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/concat
|
||||
(rx/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"})
|
||||
::dwp/force-persist)
|
||||
|
||||
;; FIXME: we should abstract this
|
||||
(->> (rx/from-atom refs/persistence-state {:emit-current-value? true})
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
|
||||
(rx/map #(dw/initialize-workspace file-id)))
|
||||
|
||||
(->> (rx/of 1)
|
||||
(rx/tap resolve)
|
||||
(rx/ignore))))))
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -10,13 +10,14 @@
|
|||
[app.common.exceptions :as ex]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.auth :as da]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.workspace :as-alias dw]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.util.globals :as glob]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.timers :as ts]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
@ -116,7 +117,7 @@
|
|||
(if show-oops?
|
||||
(st/async-emit! (rt/assign-exception e))
|
||||
(do
|
||||
(st/emit! (du/logout {:capture-redirect true}))
|
||||
(st/emit! (da/logout))
|
||||
(ts/schedule 500 #(st/emit! (ntf/warn msg)))))))
|
||||
|
||||
;; Error that happens on an active business model validation does not
|
||||
|
@ -141,7 +142,7 @@
|
|||
:timeout 3000})))
|
||||
|
||||
(= code :vern-conflict)
|
||||
(st/emit! (reload-file))
|
||||
(st/emit! (ptk/event ::dw/reload-current-file))
|
||||
|
||||
:else
|
||||
(st/async-emit! (rt/assign-exception error))))
|
||||
|
@ -212,7 +213,6 @@
|
|||
(ts/schedule
|
||||
#(st/emit! (rt/assign-exception error))))
|
||||
|
||||
|
||||
(defn- redirect-to-dashboard
|
||||
[]
|
||||
(let [team-id (:current-team-id @st/state)
|
||||
|
|
|
@ -22,22 +22,33 @@
|
|||
;; ---- Global refs
|
||||
|
||||
(def route
|
||||
(l/derived :route st/state))
|
||||
(l/derived (l/key :route) st/state))
|
||||
|
||||
(def router
|
||||
(l/derived :router st/state))
|
||||
(l/derived (l/key :router) st/state))
|
||||
|
||||
(def profile
|
||||
(l/derived :profile st/state))
|
||||
(l/derived (l/key :profile) st/state))
|
||||
|
||||
(def team
|
||||
(l/derived :team st/state))
|
||||
(l/derived (fn [state]
|
||||
(let [team-id (:current-team-id state)
|
||||
teams (:teams state)]
|
||||
(get teams team-id)))
|
||||
st/state))
|
||||
|
||||
(def project
|
||||
(l/derived (fn [state]
|
||||
(let [project-id (:current-project-id state)
|
||||
projects (:projects state)]
|
||||
(get projects project-id)))
|
||||
st/state))
|
||||
|
||||
(def permissions
|
||||
(l/derived :permissions st/state))
|
||||
(l/derived (l/key :permissions) team))
|
||||
|
||||
(def teams
|
||||
(l/derived :teams st/state))
|
||||
(l/derived (l/key :teams) st/state))
|
||||
|
||||
(def exception
|
||||
(l/derived :exception st/state))
|
||||
|
@ -54,68 +65,41 @@
|
|||
(def persistence
|
||||
(l/derived :persistence st/state))
|
||||
|
||||
;; ---- Dashboard refs
|
||||
(def projects
|
||||
(l/derived :projects st/state))
|
||||
|
||||
(def dashboard-local
|
||||
(l/derived :dashboard-local st/state))
|
||||
(def files
|
||||
(l/derived :files st/state))
|
||||
|
||||
(def dashboard-fonts
|
||||
(l/derived :dashboard-fonts st/state))
|
||||
(def shared-files
|
||||
"A derived state that points to the current list of shared
|
||||
files (without the content, only summary)"
|
||||
(l/derived :shared-files st/state))
|
||||
|
||||
(def dashboard-projects
|
||||
(l/derived :dashboard-projects st/state))
|
||||
(def libraries
|
||||
(l/derived :libraries st/state))
|
||||
|
||||
(def dashboard-files
|
||||
(l/derived :dashboard-files st/state))
|
||||
|
||||
(def dashboard-shared-files
|
||||
(l/derived :dashboard-shared-files st/state))
|
||||
|
||||
(def dashboard-search-result
|
||||
(l/derived :dashboard-search-result st/state))
|
||||
|
||||
(def dashboard-team-stats
|
||||
(l/derived :dashboard-team-stats st/state))
|
||||
|
||||
(def dashboard-team-members
|
||||
(l/derived :dashboard-team-members st/state))
|
||||
|
||||
(def dashboard-team-invitations
|
||||
(l/derived :dashboard-team-invitations st/state))
|
||||
|
||||
(def dashboard-team-webhooks
|
||||
(l/derived :dashboard-team-webhooks st/state))
|
||||
|
||||
(def dashboard-selected-project
|
||||
(l/derived (fn [state]
|
||||
(dm/get-in state [:dashboard-local :selected-project]))
|
||||
st/state))
|
||||
|
||||
(defn- dashboard-extract-selected
|
||||
(defn extract-selected-files
|
||||
[files selected]
|
||||
(let [get-file #(get files %)
|
||||
sim-file #(select-keys % [:id :name :project-id :is-shared])
|
||||
xform (comp (keep get-file)
|
||||
(map sim-file))]
|
||||
(->> (into #{} xform selected)
|
||||
(->> (sequence xform selected)
|
||||
(d/index-by :id))))
|
||||
|
||||
(def dashboard-selected-search
|
||||
(def selected-files
|
||||
(l/derived (fn [state]
|
||||
;; we need to this because :dashboard-search-result is a list
|
||||
;; of maps and we need a map of maps (using :id as key).
|
||||
(let [files (d/index-by :id (:dashboard-search-result state))]
|
||||
(->> (dm/get-in state [:dashboard-local :selected-files])
|
||||
(dashboard-extract-selected files))))
|
||||
(let [selected (get state :selected-files)
|
||||
files (get state :files)]
|
||||
(extract-selected-files files selected)))
|
||||
st/state))
|
||||
|
||||
(def dashboard-selected-files
|
||||
(l/derived (fn [state]
|
||||
(->> (dm/get-in state [:dashboard-local :selected-files])
|
||||
(dashboard-extract-selected (:dashboard-files state))))
|
||||
st/state))
|
||||
(def selected-project
|
||||
(l/derived :selected-project st/state))
|
||||
|
||||
;; ---- Workspace refs
|
||||
(def dashboard-local
|
||||
(l/derived :dashboard-local st/state))
|
||||
|
||||
(def render-state
|
||||
(l/derived :render-state st/state))
|
||||
|
@ -270,12 +254,6 @@
|
|||
(def workspace-file-typography
|
||||
(l/derived :typographies workspace-data))
|
||||
|
||||
(def workspace-project
|
||||
(l/derived :workspace-project st/state))
|
||||
|
||||
(def workspace-shared-files
|
||||
(l/derived :workspace-shared-files st/state))
|
||||
|
||||
(def workspace-local-library
|
||||
(l/derived (fn [state]
|
||||
(select-keys (:workspace-data state)
|
||||
|
@ -532,12 +510,16 @@
|
|||
|
||||
;; ---- Viewer refs
|
||||
|
||||
(defn get-viewer-objects
|
||||
[state page-id]
|
||||
(dm/get-in state [:viewer :pages page-id :objects]))
|
||||
|
||||
(defn lookup-viewer-objects-by-id
|
||||
[page-id]
|
||||
(l/derived #(wsh/lookup-viewer-objects % page-id) st/state =))
|
||||
(l/derived #(get-viewer-objects % page-id) st/state =))
|
||||
|
||||
(def viewer-data
|
||||
(l/derived :viewer st/state))
|
||||
(l/derived (l/key :viewer) st/state))
|
||||
|
||||
(def viewer-file
|
||||
(l/derived :file viewer-data))
|
||||
|
@ -563,14 +545,8 @@
|
|||
(def comments-local
|
||||
(l/derived :comments-local st/state))
|
||||
|
||||
(def users
|
||||
(l/derived :users st/state))
|
||||
|
||||
(def current-file-comments-users
|
||||
(l/derived :current-file-comments-users st/state))
|
||||
|
||||
(def current-team-comments-users
|
||||
(l/derived :current-team-comments-users st/state))
|
||||
(def profiles
|
||||
(l/derived :profiles st/state))
|
||||
|
||||
(def viewer-fullscreen?
|
||||
(l/derived (fn [state]
|
||||
|
@ -582,14 +558,11 @@
|
|||
(dm/get-in state [:viewer-local :zoom-type]))
|
||||
st/state))
|
||||
|
||||
(def workspace-thumbnails
|
||||
(l/derived :workspace-thumbnails st/state))
|
||||
|
||||
(defn workspace-thumbnail-by-id
|
||||
[object-id]
|
||||
(l/derived
|
||||
(fn [state]
|
||||
(some-> (dm/get-in state [:workspace-thumbnails object-id])
|
||||
(some-> (dm/get-in state [:thumbnails object-id])
|
||||
(cf/resolve-media)))
|
||||
st/state))
|
||||
|
||||
|
@ -635,35 +608,9 @@
|
|||
(every? (partial ctl/grid-layout-immediate-child? objects))))
|
||||
workspace-page-objects =))
|
||||
|
||||
;; FIXME: move to viewer.inspect.code
|
||||
(defn get-flex-child-viewer
|
||||
[ids page-id]
|
||||
(l/derived
|
||||
(fn [state]
|
||||
(let [objects (wsh/lookup-viewer-objects state page-id)]
|
||||
(into []
|
||||
(comp (map (d/getf objects))
|
||||
(filter (partial ctl/flex-layout-immediate-child? objects)))
|
||||
ids)))
|
||||
st/state =))
|
||||
|
||||
;; FIXME: move to viewer.inspect.code
|
||||
(defn get-viewer-objects
|
||||
([]
|
||||
(let [route (deref route)
|
||||
page-id (:page-id (:query-params route))]
|
||||
(get-viewer-objects page-id)))
|
||||
([page-id]
|
||||
(l/derived
|
||||
(fn [state]
|
||||
(let [objects (wsh/lookup-viewer-objects state page-id)]
|
||||
objects))
|
||||
st/state =)))
|
||||
|
||||
(def colorpicker
|
||||
(l/derived :colorpicker st/state))
|
||||
|
||||
|
||||
(def workspace-grid-edition
|
||||
(l/derived :workspace-grid-edition st/state))
|
||||
|
||||
|
@ -671,6 +618,7 @@
|
|||
[id]
|
||||
(l/derived #(get % id) workspace-grid-edition))
|
||||
|
||||
;; FIXME: remove
|
||||
(def current-file-id
|
||||
(l/derived :current-file-id st/state))
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
[app.common.transit :as t]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.main.data.events :as-alias ev]
|
||||
[app.main.data.event :as-alias ev]
|
||||
[app.util.http :as http]
|
||||
[app.util.sse :as sse]
|
||||
[beicon.v2.core :as rx]
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.util.router
|
||||
(ns app.main.router
|
||||
(:refer-clojure :exclude [resolve])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.util.browser-history :as bhistory]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as globals]
|
||||
|
@ -28,11 +28,10 @@
|
|||
(r/map->Match data))
|
||||
|
||||
(defn resolve
|
||||
([router id] (resolve router id {} {}))
|
||||
([router id path-params] (resolve router id path-params {}))
|
||||
([router id path-params query-params]
|
||||
(when-let [match (r/match-by-name router id path-params)]
|
||||
(r/match->path match query-params))))
|
||||
([router id] (resolve router id {}))
|
||||
([router id params]
|
||||
(when-let [match (r/match-by-name router id)]
|
||||
(r/match->path match params))))
|
||||
|
||||
(defn create
|
||||
[routes]
|
||||
|
@ -63,6 +62,9 @@
|
|||
(defn navigated
|
||||
[match]
|
||||
(ptk/reify ::navigated
|
||||
IDeref
|
||||
(-deref [_] match)
|
||||
|
||||
ev/Event
|
||||
(-data [_]
|
||||
(let [route (dm/get-in match [:data :name])
|
||||
|
@ -77,25 +79,29 @@
|
|||
(assoc :route match)
|
||||
(dissoc :exception)))))
|
||||
|
||||
(defn navigate*
|
||||
[id path-params query-params replace]
|
||||
(defn navigate
|
||||
[id params & {:keys [::replace ::new-window] :as options}]
|
||||
(ptk/reify ::navigate
|
||||
IDeref
|
||||
(-deref [_]
|
||||
{:id id
|
||||
:path-params path-params
|
||||
:query-params query-params
|
||||
:replace replace})
|
||||
:params params
|
||||
:options options})
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [router (:router state)
|
||||
history (:history state)
|
||||
path (resolve router id path-params query-params)]
|
||||
(ts/asap
|
||||
#(if ^boolean replace
|
||||
(bhistory/replace-token! history path)
|
||||
(bhistory/set-token! history path)))))))
|
||||
path (resolve router id params)]
|
||||
|
||||
(if ^boolean new-window
|
||||
(let [name (or (::window-name options) "_blank")
|
||||
uri (assoc cf/public-uri :fragment path)]
|
||||
(dom/open-new-window uri name nil))
|
||||
(ts/asap
|
||||
#(if ^boolean replace
|
||||
(bhistory/replace-token! history path)
|
||||
(bhistory/set-token! history path))))))))
|
||||
|
||||
(defn assign-exception
|
||||
[error]
|
||||
|
@ -107,27 +113,14 @@
|
|||
(assoc state :exception error)))))
|
||||
|
||||
(defn nav
|
||||
([id] (nav id nil nil))
|
||||
([id path-params] (nav id path-params nil))
|
||||
([id path-params query-params] (navigate* id path-params query-params false)))
|
||||
([id] (navigate id nil))
|
||||
([id params] (navigate id params))
|
||||
([id params & {:as options}]
|
||||
(navigate id params options)))
|
||||
|
||||
(defn nav'
|
||||
([id] (nav id nil nil))
|
||||
([id path-params] (nav id path-params nil))
|
||||
([id path-params query-params] (navigate* id path-params query-params true)))
|
||||
|
||||
(def navigate nav)
|
||||
|
||||
(defn nav-new-window*
|
||||
[{:keys [rname path-params query-params name]}]
|
||||
(ptk/reify ::nav-new-window
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [router (:router state)
|
||||
path (resolve router rname path-params query-params)
|
||||
name (or name "_blank")
|
||||
uri (assoc cf/public-uri :fragment path)]
|
||||
(dom/open-new-window uri name nil)))))
|
||||
(defn get-params
|
||||
[state]
|
||||
(dm/get-in state [:route :params :query]))
|
||||
|
||||
(defn nav-back
|
||||
[]
|
|
@ -60,7 +60,7 @@
|
|||
:app.main.data.workspace.persistence/update-persistence-status
|
||||
:app.main.data.websocket/send-message
|
||||
:app.main.data.workspace.notifications/handle-pointer-send
|
||||
:app.util.router/assign-exception}]
|
||||
:app.main.router/assign-exception}]
|
||||
(->> (rx/merge
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? :app.main.data.changes/commit))
|
||||
|
|
|
@ -6,14 +6,20 @@
|
|||
|
||||
(ns app.main.ui
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.config :as cf]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.debug.icons-preview :refer [icons-preview]]
|
||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.error-boundary :refer [error-boundary*]]
|
||||
[app.main.ui.exports.files]
|
||||
[app.main.ui.frame-preview :as frame-preview]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.notifications :as notifications]
|
||||
[app.main.ui.onboarding.newsletter :refer [onboarding-newsletter]]
|
||||
[app.main.ui.onboarding.questions :refer [questions-modal]]
|
||||
|
@ -22,6 +28,8 @@
|
|||
[app.main.ui.static :as static]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def auth-page
|
||||
|
@ -31,23 +39,117 @@
|
|||
(mf/lazy-component app.main.ui.auth.verify-token/verify-token))
|
||||
|
||||
(def viewer-page
|
||||
(mf/lazy-component app.main.ui.viewer/viewer))
|
||||
(mf/lazy-component app.main.ui.viewer/viewer*))
|
||||
|
||||
(def dashboard-page
|
||||
(mf/lazy-component app.main.ui.dashboard/dashboard))
|
||||
(mf/lazy-component app.main.ui.dashboard/dashboard*))
|
||||
|
||||
(def settings-page
|
||||
(mf/lazy-component app.main.ui.settings/settings))
|
||||
|
||||
(def workspace-page
|
||||
(mf/lazy-component app.main.ui.workspace/workspace))
|
||||
(mf/lazy-component app.main.ui.workspace/workspace*))
|
||||
|
||||
(mf/defc main-page
|
||||
(mf/defc workspace-legacy-redirect*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [project-id file-id page-id layout]}]
|
||||
(mf/with-effect []
|
||||
(->> (rp/cmd! :get-project {:id project-id})
|
||||
(rx/subs! (fn [{:keys [team-id]}]
|
||||
(st/emit! (dcm/go-to-workspace :team-id team-id
|
||||
:file-id file-id
|
||||
:page-id page-id
|
||||
:layout layout)))
|
||||
ptk/handle-error)))
|
||||
|
||||
[:> loader*
|
||||
{:title (tr "labels.loading")
|
||||
:overlay true}])
|
||||
|
||||
(mf/defc dashboard-legacy-redirect*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [section team-id project-id search-term plugin-url]}]
|
||||
(let [section (case section
|
||||
:dashboard-legacy-search
|
||||
:dashboard-search
|
||||
:dashboard-legacy-projects
|
||||
:dashboard-recent
|
||||
:dashboard-legacy-files
|
||||
:dashboard-files
|
||||
:dashboard-legacy-libraries
|
||||
:dashboard-libraries
|
||||
:dashboard-legacy-fonts
|
||||
:dashboard-fonts
|
||||
:dashboard-legacy-font-providers
|
||||
:dashboard-font-providers
|
||||
:dashboard-legacy-team-members
|
||||
:dashboard-members
|
||||
:dashboard-legacy-team-invitations
|
||||
:dashboard-invitations
|
||||
:dashboard-legacy-team-webhooks
|
||||
:dashboard-webhooks
|
||||
:dashboard-legacy-team-settings
|
||||
:dashboard-settings)]
|
||||
|
||||
(mf/with-effect []
|
||||
(let [params {:team-id team-id
|
||||
:project-id project-id
|
||||
:search-term search-term
|
||||
:plugin plugin-url}]
|
||||
(st/emit! (rt/nav section (d/without-nils params)))))
|
||||
|
||||
[:> loader*
|
||||
{:title (tr "labels.loading")
|
||||
:overlay true}]))
|
||||
|
||||
(mf/defc viewer-legacy-redirect*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [page-id file-id section index share-id interactions-mode frame-id share]}]
|
||||
(mf/with-effect []
|
||||
(let [params {:page-id page-id
|
||||
:file-id file-id
|
||||
:section section
|
||||
:index index
|
||||
:share-id share-id
|
||||
:interactions-mode interactions-mode
|
||||
:frame-id frame-id
|
||||
:share share}]
|
||||
(st/emit! (rt/nav :viewer (d/without-nils params)))))
|
||||
|
||||
[:> loader*
|
||||
{:title (tr "labels.loading")
|
||||
:overlay true}])
|
||||
|
||||
(mf/defc team-container*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [team-id children]}]
|
||||
|
||||
(mf/with-effect [team-id]
|
||||
(st/emit! (dtm/initialize-team team-id))
|
||||
(fn []
|
||||
(st/emit! (dtm/finalize-team team-id))))
|
||||
|
||||
(let [team (mf/deref refs/team)]
|
||||
(when (= team-id (:id team))
|
||||
[:& (mf/provider ctx/current-team-id) {:value team-id}
|
||||
[:& (mf/provider ctx/permissions) {:value (:permissions team)}
|
||||
;; The `:key` is mandatory here because we want to reinitialize
|
||||
;; all dom tree instead of simple rerender.
|
||||
[:* {:key (str team-id)} children]]])))
|
||||
|
||||
(mf/defc page*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [route profile]}]
|
||||
(let [{:keys [data params]} route
|
||||
props (get profile :props)
|
||||
props (get profile :props)
|
||||
section (get data :name)
|
||||
|
||||
|
||||
show-question-modal?
|
||||
(and (contains? cf/flags :onboarding)
|
||||
(not (:onboarding-viewed props))
|
||||
|
@ -72,7 +174,7 @@
|
|||
(not= "0.0" (:main cf/version)))]
|
||||
|
||||
[:& (mf/provider ctx/current-route) {:value route}
|
||||
(case (:name data)
|
||||
(case section
|
||||
(:auth-login
|
||||
:auth-register
|
||||
:auth-register-validate
|
||||
|
@ -96,66 +198,53 @@
|
|||
[:& icons-preview])
|
||||
|
||||
(:dashboard-search
|
||||
:dashboard-projects
|
||||
:dashboard-recent
|
||||
:dashboard-files
|
||||
:dashboard-libraries
|
||||
:dashboard-fonts
|
||||
:dashboard-font-providers
|
||||
:dashboard-team-members
|
||||
:dashboard-team-invitations
|
||||
:dashboard-team-webhooks
|
||||
:dashboard-team-settings)
|
||||
[:?
|
||||
#_[:& app.main.ui.releases/release-notes-modal {:version "2.3"}]
|
||||
#_[:& app.main.ui.onboarding/onboarding-templates-modal]
|
||||
#_[:& app.main.ui.onboarding/onboarding-modal]
|
||||
#_[:& app.main.ui.onboarding.team-choice/onboarding-team-modal]
|
||||
:dashboard-members
|
||||
:dashboard-invitations
|
||||
:dashboard-webhooks
|
||||
:dashboard-settings)
|
||||
(let [params (get params :query)
|
||||
team-id (some-> params :team-id uuid)
|
||||
project-id (some-> params :project-id uuid)
|
||||
search-term (some-> params :search-term)
|
||||
plugin-url (some-> params :plugin)]
|
||||
[:?
|
||||
#_[:& app.main.ui.releases/release-notes-modal {:version "2.3"}]
|
||||
#_[:& app.main.ui.onboarding/onboarding-templates-modal]
|
||||
#_[:& app.main.ui.onboarding/onboarding-modal]
|
||||
#_[:& app.main.ui.onboarding.team-choice/onboarding-team-modal]
|
||||
|
||||
(cond
|
||||
show-question-modal?
|
||||
[:& questions-modal]
|
||||
(cond
|
||||
show-question-modal?
|
||||
[:& questions-modal]
|
||||
|
||||
show-newsletter-modal?
|
||||
[:& onboarding-newsletter]
|
||||
show-newsletter-modal?
|
||||
[:& onboarding-newsletter]
|
||||
|
||||
show-team-modal?
|
||||
[:& onboarding-team-modal {:go-to-team? true}]
|
||||
show-team-modal?
|
||||
[:& onboarding-team-modal {:go-to-team? true}]
|
||||
|
||||
show-release-modal?
|
||||
[:& release-notes-modal {:version (:main cf/version)}])
|
||||
show-release-modal?
|
||||
[:& release-notes-modal {:version (:main cf/version)}])
|
||||
|
||||
[:& dashboard-page {:route route :profile profile}]]
|
||||
:viewer
|
||||
(let [{:keys [query-params path-params]} route
|
||||
{:keys [index share-id section page-id interactions-mode frame-id share]
|
||||
:or {section :interactions interactions-mode :show-on-click}} query-params
|
||||
{:keys [file-id]} path-params]
|
||||
[:? {}
|
||||
(if (:token query-params)
|
||||
[:> static/error-container* {}
|
||||
[:div.image i/detach]
|
||||
[:div.main-message (tr "viewer.breaking-change.message")]
|
||||
[:div.desc-message (tr "viewer.breaking-change.description")]]
|
||||
|
||||
[:& viewer-page
|
||||
{:page-id page-id
|
||||
:file-id file-id
|
||||
:section section
|
||||
:index index
|
||||
:share-id share-id
|
||||
:interactions-mode (keyword interactions-mode)
|
||||
:interactions-show? (case (keyword interactions-mode)
|
||||
:hide false
|
||||
:show true
|
||||
:show-on-click false)
|
||||
:frame-id frame-id
|
||||
:share share}])])
|
||||
[:> team-container* {:team-id team-id}
|
||||
[:> dashboard-page {:profile profile
|
||||
:section section
|
||||
:team-id team-id
|
||||
:search-term search-term
|
||||
:plugin-url plugin-url
|
||||
:project-id project-id}]]])
|
||||
|
||||
:workspace
|
||||
(let [project-id (some-> params :path :project-id uuid)
|
||||
file-id (some-> params :path :file-id uuid)
|
||||
page-id (some-> params :query :page-id uuid)
|
||||
layout (some-> params :query :layout keyword)]
|
||||
(let [params (get params :query)
|
||||
team-id (some-> params :team-id uuid)
|
||||
file-id (some-> params :file-id uuid)
|
||||
page-id (some-> params :page-id uuid)
|
||||
layout (some-> params :layout keyword)]
|
||||
[:? {}
|
||||
(when (cf/external-feature-flag "onboarding-03" "test")
|
||||
(cond
|
||||
|
@ -171,11 +260,87 @@
|
|||
show-release-modal?
|
||||
[:& release-notes-modal {:version (:main cf/version)}]))
|
||||
|
||||
[:& workspace-page {:project-id project-id
|
||||
:file-id file-id
|
||||
:page-id page-id
|
||||
:layout-name layout
|
||||
:key file-id}]])
|
||||
[:> team-container* {:team-id team-id}
|
||||
[:> workspace-page {:team-id team-id
|
||||
:file-id file-id
|
||||
:page-id page-id
|
||||
:layout-name layout
|
||||
:key file-id}]]])
|
||||
|
||||
:viewer
|
||||
(let [params (get params :query)
|
||||
index (some-> (:index params) parse-long)
|
||||
share-id (some-> (:share-id params) parse-uuid)
|
||||
section (or (some-> (:section params) keyword)
|
||||
:interactions)
|
||||
|
||||
file-id (some-> (:file-id params) parse-uuid)
|
||||
page-id (some-> (:page-id params) parse-uuid)
|
||||
imode (or (some-> (:interactions-mode params) keyword)
|
||||
:show-on-click)
|
||||
frame-id (some-> (:frame-id params) parse-uuid)
|
||||
share (:share params)]
|
||||
|
||||
[:? {}
|
||||
[:> viewer-page
|
||||
{:page-id page-id
|
||||
:file-id file-id
|
||||
:frame-id frame-id
|
||||
:section section
|
||||
:index index
|
||||
:share-id share-id
|
||||
:interactions-mode imode
|
||||
:share share}]])
|
||||
|
||||
|
||||
:workspace-legacy
|
||||
(let [project-id (some-> params :path :project-id uuid)
|
||||
file-id (some-> params :path :file-id uuid)
|
||||
page-id (some-> params :query :page-id uuid)
|
||||
layout (some-> params :query :layout keyword)]
|
||||
|
||||
[:> workspace-legacy-redirect*
|
||||
{:project-id project-id
|
||||
:file-id file-id
|
||||
:page-id page-id
|
||||
:layout layout}])
|
||||
|
||||
(:dashboard-legacy-search
|
||||
:dashboard-legacy-projects
|
||||
:dashboard-legacy-files
|
||||
:dashboard-legacy-libraries
|
||||
:dashboard-legacy-fonts
|
||||
:dashboard-legacy-font-providers
|
||||
:dashboard-legacy-team-members
|
||||
:dashboard-legacy-team-invitations
|
||||
:dashboard-legacy-team-webhooks
|
||||
:dashboard-legacy-team-settings)
|
||||
(let [team-id (some-> params :path :team-id uuid)
|
||||
project-id (some-> params :path :project-id uuid)
|
||||
search-term (some-> params :query :search-term)
|
||||
plugin-url (some-> params :query :plugin)]
|
||||
[:> dashboard-legacy-redirect*
|
||||
{:team-id team-id
|
||||
:section section
|
||||
:project-id project-id
|
||||
:search-term search-term
|
||||
:plugin-url plugin-url}])
|
||||
|
||||
:viewer-legacy
|
||||
(let [{:keys [query-params path-params]} route
|
||||
{:keys [index share-id section page-id interactions-mode frame-id share]
|
||||
:or {section :interactions interactions-mode :show-on-click}} query-params
|
||||
{:keys [file-id]} path-params]
|
||||
|
||||
[:> viewer-legacy-redirect*
|
||||
{:page-id page-id
|
||||
:file-id file-id
|
||||
:section section
|
||||
:index index
|
||||
:share-id share-id
|
||||
:interactions-mode (keyword interactions-mode)
|
||||
:frame-id frame-id
|
||||
:share share}])
|
||||
|
||||
:frame-preview
|
||||
[:& frame-preview/frame-preview]
|
||||
|
@ -199,4 +364,4 @@
|
|||
[:> error-boundary* {:fallback static/internal-error*}
|
||||
[:& notifications/current-notification]
|
||||
(when route
|
||||
[:& main-page {:route route :profile profile}])])]]))
|
||||
[:> page* {:route route :profile profile}])])]]))
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.auth :as da]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.auth.login :refer [login-page]]
|
||||
[app.main.ui.auth.recovery :refer [recovery-page]]
|
||||
|
@ -19,7 +19,6 @@
|
|||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
||||
(mf/defc auth
|
||||
{::mf/props :obj}
|
||||
[{:keys [route]}]
|
||||
|
@ -35,7 +34,7 @@
|
|||
|
||||
(mf/with-effect [error]
|
||||
(when error
|
||||
(st/emit! (du/show-redirect-error error))))
|
||||
(st/emit! (da/show-redirect-error error))))
|
||||
|
||||
[:main {:class (stl/css :auth-section)}
|
||||
(when show-login-icon
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
[app.common.logging :as log]
|
||||
[app.common.schema :as sm]
|
||||
[app.config :as cf]
|
||||
[app.main.data.auth :as da]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.button-link :as bl]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
|
@ -22,7 +23,6 @@
|
|||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :as s]
|
||||
[beicon.v2.core :as rx]
|
||||
[rumext.v2 :as mf]))
|
||||
|
@ -43,7 +43,7 @@
|
|||
|
||||
(defn create-demo-profile
|
||||
[]
|
||||
(st/emit! (du/create-demo-profile)))
|
||||
(st/emit! (da/create-demo-profile)))
|
||||
|
||||
(defn- store-login-redirect
|
||||
[save-login-redirect]
|
||||
|
@ -124,7 +124,7 @@
|
|||
(mf/use-fn
|
||||
(fn [data]
|
||||
(when-let [token (:invitation-token data)]
|
||||
(st/emit! (rt/nav :auth-verify-token {} {:token token})))))
|
||||
(st/emit! (rt/nav :auth-verify-token {:token token})))))
|
||||
|
||||
on-success
|
||||
(fn [data]
|
||||
|
@ -140,7 +140,7 @@
|
|||
(let [params (with-meta (:clean-data @form)
|
||||
{:on-error on-error
|
||||
:on-success on-success})]
|
||||
(st/emit! (du/login params)))))
|
||||
(st/emit! (da/login params)))))
|
||||
|
||||
on-submit-ldap
|
||||
(mf/use-callback
|
||||
|
@ -154,7 +154,7 @@
|
|||
params (with-meta params
|
||||
{:on-error on-error
|
||||
:on-success on-success})]
|
||||
(st/emit! (du/login-with-ldap params)))))
|
||||
(st/emit! (da/login-with-ldap params)))))
|
||||
|
||||
default-recovery-req
|
||||
(mf/use-fn
|
||||
|
@ -283,7 +283,7 @@
|
|||
[{:keys [params] :as props}]
|
||||
(let [go-register
|
||||
(mf/use-fn
|
||||
#(st/emit! (rt/nav :auth-register {} params)))]
|
||||
#(st/emit! (rt/nav :auth-register params)))]
|
||||
|
||||
[:div {:class (stl/css :auth-form-wrapper)}
|
||||
[:h1 {:class (stl/css :auth-title)
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema:recovery-form
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.components.link :as lk]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
|
|
@ -10,16 +10,16 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.schema :as sm]
|
||||
[app.config :as cf]
|
||||
[app.main.data.auth :as da]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.auth.login :as login]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.components.link :as lk]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :as storage]
|
||||
[beicon.v2.core :as rx]
|
||||
[rumext.v2 :as mf]))
|
||||
|
@ -74,7 +74,7 @@
|
|||
on-success (fn [data]
|
||||
(if (fn? on-success-callback)
|
||||
(on-success-callback data)
|
||||
(st/emit! (rt/nav :auth-register-validate {} data))))]
|
||||
(st/emit! (rt/nav :auth-register-validate data))))]
|
||||
|
||||
(->> (rp/cmd! :prepare-register-profile cdata)
|
||||
(rx/map #(merge % params))
|
||||
|
@ -131,7 +131,7 @@
|
|||
[:div {:class (stl/css :links)}
|
||||
[:div {:class (stl/css :account)}
|
||||
[:span {:class (stl/css :account-text)} (tr "auth.already-have-account") " "]
|
||||
[:& lk/link {:action #(st/emit! (rt/nav :auth-login {} params))
|
||||
[:& lk/link {:action #(st/emit! (rt/nav :auth-login params))
|
||||
:class (stl/css :account-link)
|
||||
:data-testid "login-here-link"}
|
||||
(tr "auth.login-here")]]
|
||||
|
@ -191,10 +191,10 @@
|
|||
(cond
|
||||
(some? (:invitation-token params))
|
||||
(let [token (:invitation-token params)]
|
||||
(st/emit! (rt/nav :auth-verify-token {} {:token token})))
|
||||
(st/emit! (rt/nav :auth-verify-token {:token token})))
|
||||
|
||||
(:is-active params)
|
||||
(st/emit! (du/login-from-register))
|
||||
(st/emit! (da/login-from-register))
|
||||
|
||||
:else
|
||||
(do
|
||||
|
@ -257,7 +257,7 @@
|
|||
|
||||
[:div {:class (stl/css :links)}
|
||||
[:div {:class (stl/css :go-back)}
|
||||
[:& lk/link {:action #(st/emit! (rt/nav :auth-register {} {}))
|
||||
[:& lk/link {:action #(st/emit! (rt/nav :auth-register {}))
|
||||
:class (stl/css :go-back-link)}
|
||||
(tr "labels.go-back")]]]])
|
||||
|
||||
|
|
|
@ -6,15 +6,17 @@
|
|||
|
||||
(ns app.main.ui.auth.verify-token
|
||||
(:require
|
||||
[app.main.data.auth :as da]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.static :as static]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.timers :as ts]
|
||||
[beicon.v2.core :as rx]
|
||||
[rumext.v2 :as mf]))
|
||||
|
@ -25,7 +27,7 @@
|
|||
[data]
|
||||
(let [msg (tr "dashboard.notifications.email-verified-successfully")]
|
||||
(ts/schedule 1000 #(st/emit! (ntf/success msg)))
|
||||
(st/emit! (du/login-from-token data))))
|
||||
(st/emit! (da/login-from-token data))))
|
||||
|
||||
(defmethod handle-token :change-email
|
||||
[_data]
|
||||
|
@ -36,21 +38,22 @@
|
|||
|
||||
(defmethod handle-token :auth
|
||||
[tdata]
|
||||
(st/emit! (du/login-from-token tdata)))
|
||||
(st/emit! (da/login-from-token tdata)))
|
||||
|
||||
(defmethod handle-token :team-invitation
|
||||
[tdata]
|
||||
(case (:state tdata)
|
||||
:created
|
||||
(st/emit!
|
||||
(ntf/success (tr "auth.notifications.team-invitation-accepted"))
|
||||
(du/fetch-profile)
|
||||
(rt/nav :dashboard-projects {:team-id (:team-id tdata)}))
|
||||
(let [team-id (:team-id tdata)]
|
||||
(st/emit!
|
||||
(ntf/success (tr "auth.notifications.team-invitation-accepted"))
|
||||
(du/fetch-profile)
|
||||
(dcm/go-to-dashboard-recent :team-id team-id)))
|
||||
|
||||
:pending
|
||||
(let [token (:invitation-token tdata)
|
||||
route-id (:redirect-to tdata :auth-register)]
|
||||
(st/emit! (rt/nav route-id {} {:invitation-token token})))))
|
||||
(st/emit! (rt/nav route-id {:invitation-token token})))))
|
||||
|
||||
(defmethod handle-token :default
|
||||
[_tdata]
|
||||
|
|
|
@ -253,8 +253,8 @@
|
|||
:disabled disabled?}]]]))
|
||||
|
||||
(mf/defc comment-item
|
||||
[{:keys [comment thread users origin] :as props}]
|
||||
(let [owner (get users (:owner-id comment))
|
||||
[{:keys [comment thread profiles origin] :as props}]
|
||||
(let [owner (get profiles (:owner-id comment))
|
||||
profile (mf/deref refs/profile)
|
||||
options (mf/deref comments-local-options)
|
||||
edition? (mf/use-state false)
|
||||
|
@ -384,7 +384,7 @@
|
|||
|
||||
(mf/defc thread-comments
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [thread zoom users origin position-modifier viewport]}]
|
||||
[{:keys [thread zoom profiles origin position-modifier viewport]}]
|
||||
(let [ref (mf/use-ref)
|
||||
thread-id (:id thread)
|
||||
thread-pos (:position thread)
|
||||
|
@ -435,13 +435,13 @@
|
|||
|
||||
[:div {:class (stl/css :comments)}
|
||||
[:& comment-item {:comment comment
|
||||
:users users
|
||||
:profiles profiles
|
||||
:thread thread
|
||||
:origin origin}]
|
||||
(for [item (rest comments)]
|
||||
[:* {:key (dm/str (:id item))}
|
||||
[:& comment-item {:comment item
|
||||
:users users
|
||||
:profiles profiles
|
||||
:origin origin}]])]
|
||||
[:& reply-form {:thread thread}]
|
||||
[:div {:ref ref}]])))
|
||||
|
@ -573,8 +573,8 @@
|
|||
[:span (:seqn thread)]]))
|
||||
|
||||
(mf/defc comment-thread
|
||||
[{:keys [item users on-click]}]
|
||||
(let [owner (get users (:owner-id item))
|
||||
[{:keys [item profiles on-click]}]
|
||||
(let [owner (get profiles (:owner-id item))
|
||||
on-click*
|
||||
(mf/use-fn
|
||||
(mf/deps item)
|
||||
|
@ -613,7 +613,7 @@
|
|||
[:span {:class (stl/css :new-replies)} (str unread " new replies")]))])]]))
|
||||
|
||||
(mf/defc comment-thread-group
|
||||
[{:keys [group users on-thread-click]}]
|
||||
[{:keys [group profiles on-thread-click]}]
|
||||
[:div {:class (stl/css :thread-group)}
|
||||
(if (:file-name group)
|
||||
[:div {:class (stl/css :section-title)
|
||||
|
@ -631,5 +631,5 @@
|
|||
[:& comment-thread
|
||||
{:item item
|
||||
:on-click on-thread-click
|
||||
:users users
|
||||
:profiles profiles
|
||||
:key (:id item)}])]])
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.events :as-alias ev]
|
||||
[app.main.data.event :as-alias ev]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.timers :as tm]
|
||||
|
|
|
@ -33,4 +33,4 @@
|
|||
(def is-component? (mf/create-context false))
|
||||
(def sidebar (mf/create-context nil))
|
||||
|
||||
(def team-permissions (mf/create-context nil))
|
||||
(def permissions (mf/create-context nil))
|
||||
|
|
|
@ -7,62 +7,46 @@
|
|||
(ns app.main.ui.dashboard
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.dashboard.shortcuts :as sc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as notif]
|
||||
[app.main.data.plugins :as dp]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.dashboard.files :refer [files-section]]
|
||||
[app.main.ui.dashboard.fonts :refer [fonts-page font-providers-page]]
|
||||
[app.main.ui.dashboard.files :refer [files-section*]]
|
||||
[app.main.ui.dashboard.fonts :refer [fonts-page* font-providers-page*]]
|
||||
[app.main.ui.dashboard.import]
|
||||
[app.main.ui.dashboard.libraries :refer [libraries-page]]
|
||||
[app.main.ui.dashboard.projects :refer [projects-section]]
|
||||
[app.main.ui.dashboard.search :refer [search-page]]
|
||||
[app.main.ui.dashboard.sidebar :refer [sidebar]]
|
||||
[app.main.ui.dashboard.team :refer [team-settings-page team-members-page team-invitations-page team-webhooks-page]]
|
||||
[app.main.ui.dashboard.templates :refer [templates-section]]
|
||||
[app.main.ui.dashboard.libraries :refer [libraries-page*]]
|
||||
[app.main.ui.dashboard.projects :refer [projects-section*]]
|
||||
[app.main.ui.dashboard.search :refer [search-page*]]
|
||||
[app.main.ui.dashboard.sidebar :refer [sidebar*]]
|
||||
[app.main.ui.dashboard.team :refer [team-settings-page* team-members-page* team-invitations-page* webhooks-page*]]
|
||||
[app.main.ui.dashboard.templates :refer [templates-section*]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.workspace.plugins]
|
||||
[app.plugins.register :as preg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.object :as obj]
|
||||
[app.util.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[goog.events :as events]
|
||||
[okulary.core :as l]
|
||||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn ^boolean uuid-str?
|
||||
[s]
|
||||
(and (string? s)
|
||||
(boolean (re-seq us/uuid-rx s))))
|
||||
|
||||
(defn- parse-params
|
||||
[route]
|
||||
(let [search-term (get-in route [:params :query :search-term])
|
||||
team-id (get-in route [:params :path :team-id])
|
||||
project-id (get-in route [:params :path :project-id])]
|
||||
(cond-> {:search-term search-term}
|
||||
(uuid-str? team-id)
|
||||
(assoc :team-id (uuid team-id))
|
||||
|
||||
(uuid-str? project-id)
|
||||
(assoc :project-id (uuid project-id)))))
|
||||
|
||||
(mf/defc dashboard-content
|
||||
[{:keys [team projects project section search-term profile] :as props}]
|
||||
(mf/defc dashboard-content*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [team projects project section search-term profile default-project]}]
|
||||
(let [container (mf/use-ref)
|
||||
content-width (mf/use-state 0)
|
||||
|
||||
project-id (:id project)
|
||||
team-id (:id team)
|
||||
|
||||
|
@ -72,10 +56,7 @@
|
|||
file-menu-open? (:menu-open dashboard-local)
|
||||
|
||||
default-project-id
|
||||
(mf/with-memo [projects]
|
||||
(->> (vals projects)
|
||||
(d/seek :is-default)
|
||||
(:id)))
|
||||
(get default-project :id)
|
||||
|
||||
on-resize
|
||||
(mf/use-fn
|
||||
|
@ -88,7 +69,7 @@
|
|||
(mf/use-fn
|
||||
#(st/emit! (dd/clear-selected-files)))
|
||||
|
||||
show-templates
|
||||
show-templates?
|
||||
(and (contains? cf/flags :dashboard-templates-section)
|
||||
(:can-edit permissions))]
|
||||
|
||||
|
@ -103,61 +84,64 @@
|
|||
:on-click clear-selected-fn
|
||||
:ref container}
|
||||
(case section
|
||||
:dashboard-projects
|
||||
:dashboard-recent
|
||||
[:*
|
||||
[:& projects-section
|
||||
[:> projects-section*
|
||||
{:team team
|
||||
:projects projects
|
||||
:profile profile
|
||||
:default-project-id default-project-id}]
|
||||
:profile profile}]
|
||||
|
||||
(when show-templates
|
||||
[:& templates-section {:profile profile
|
||||
:project-id project-id
|
||||
:team-id team-id
|
||||
:default-project-id default-project-id
|
||||
:content-width @content-width}])]
|
||||
(when ^boolean show-templates?
|
||||
[:> templates-section*
|
||||
{:profile profile
|
||||
:project-id project-id
|
||||
:team-id team-id
|
||||
:default-project-id default-project-id
|
||||
:content-width @content-width}])]
|
||||
|
||||
:dashboard-fonts
|
||||
[:& fonts-page {:team team}]
|
||||
[:> fonts-page* {:team team}]
|
||||
|
||||
:dashboard-font-providers
|
||||
[:& font-providers-page {:team team}]
|
||||
[:> font-providers-page* {:team team}]
|
||||
|
||||
:dashboard-files
|
||||
(when project
|
||||
[:*
|
||||
[:& files-section {:team team :project project}]
|
||||
(when show-templates
|
||||
[:& templates-section {:profile profile
|
||||
:team-id team-id
|
||||
:project-id project-id
|
||||
:default-project-id default-project-id
|
||||
:content-width @content-width}])])
|
||||
[:> files-section* {:team team
|
||||
:project project}]
|
||||
(when ^boolean show-templates?
|
||||
[:> templates-section*
|
||||
{:profile profile
|
||||
:team-id team-id
|
||||
:project-id project-id
|
||||
:default-project-id default-project-id
|
||||
:content-width @content-width}])])
|
||||
|
||||
:dashboard-search
|
||||
[:& search-page {:team team
|
||||
:search-term search-term}]
|
||||
[:> search-page* {:team team
|
||||
:search-term search-term}]
|
||||
|
||||
:dashboard-libraries
|
||||
[:& libraries-page {:team team}]
|
||||
[:> libraries-page* {:team team
|
||||
:default-project default-project}]
|
||||
|
||||
:dashboard-team-members
|
||||
[:& team-members-page {:team team :profile profile}]
|
||||
:dashboard-members
|
||||
[:> team-members-page* {:team team :profile profile}]
|
||||
|
||||
:dashboard-team-invitations
|
||||
[:& team-invitations-page {:team team}]
|
||||
:dashboard-invitations
|
||||
[:> team-invitations-page* {:team team}]
|
||||
|
||||
:dashboard-team-webhooks
|
||||
[:& team-webhooks-page {:team team}]
|
||||
:dashboard-webhooks
|
||||
[:> webhooks-page* {:team team}]
|
||||
|
||||
:dashboard-team-settings
|
||||
[:& team-settings-page {:team team :profile profile}]
|
||||
:dashboard-settings
|
||||
[:> team-settings-page* {:team team :profile profile}]
|
||||
|
||||
nil)]))
|
||||
|
||||
(def ref:dashboard-initialized
|
||||
(l/derived :current-team-initialized st/state))
|
||||
(l/derived :team-initialized st/state))
|
||||
|
||||
(defn use-plugin-register
|
||||
[plugin-url team-id project-id]
|
||||
|
@ -167,8 +151,9 @@
|
|||
(st/emit!
|
||||
(dp/delay-open-plugin plugin)
|
||||
(rt/nav :workspace
|
||||
{:project-id project-id :file-id id}
|
||||
{:page-id (dm/get-in data [:pages 0])})))
|
||||
{:page-id (dm/get-in data [:pages 0])
|
||||
:project-id project-id
|
||||
:file-id id})))
|
||||
|
||||
create-file!
|
||||
(fn [plugin]
|
||||
|
@ -198,11 +183,11 @@
|
|||
:on-accept
|
||||
#(do (preg/install-plugin! plugin)
|
||||
(st/emit! (modal/hide)
|
||||
(rt/nav :dashboard-projects {:team-id team-id})
|
||||
(rt/nav :dashboard-recent {:team-id team-id})
|
||||
(open-try-out-dialog plugin)))
|
||||
:on-close
|
||||
#(st/emit! (modal/hide)
|
||||
(rt/nav :dashboard-projects {:team-id team-id}))}))]
|
||||
(rt/nav :dashboard-recent {:team-id team-id}))}))]
|
||||
|
||||
(mf/with-layout-effect
|
||||
[plugin-url team-id project-id]
|
||||
|
@ -218,33 +203,29 @@
|
|||
(fn [_]
|
||||
(st/emit! (notif/error "The plugin URL is incorrect")))))))))
|
||||
|
||||
(mf/defc dashboard
|
||||
(mf/defc dashboard*
|
||||
{::mf/props :obj}
|
||||
[{:keys [route profile]}]
|
||||
(let [section (get-in route [:data :name])
|
||||
params (parse-params route)
|
||||
[{:keys [profile project-id team-id search-term plugin-url section]}]
|
||||
(let [team (mf/deref refs/team)
|
||||
projects (mf/deref refs/projects)
|
||||
|
||||
project-id (:project-id params)
|
||||
project (get projects project-id)
|
||||
projects (mf/with-memo [projects team-id]
|
||||
(->> (vals projects)
|
||||
(filterv #(= team-id (:team-id %)))))
|
||||
|
||||
team-id (:team-id params)
|
||||
search-term (:search-term params)
|
||||
|
||||
plugin-url (-> route :query-params :plugin)
|
||||
|
||||
team (mf/deref refs/team)
|
||||
projects (mf/deref refs/dashboard-projects)
|
||||
project (get projects project-id)
|
||||
|
||||
default-project (->> projects vals (d/seek :is-default))
|
||||
|
||||
initialized? (mf/deref ref:dashboard-initialized)]
|
||||
default-project
|
||||
(mf/with-memo [projects]
|
||||
(->> projects
|
||||
(filter :is-default)
|
||||
(first)))]
|
||||
|
||||
(hooks/use-shortcuts ::dashboard sc/shortcuts)
|
||||
|
||||
(mf/with-effect [team-id]
|
||||
(st/emit! (dd/initialize {:id team-id}))
|
||||
(mf/with-effect []
|
||||
(st/emit! (dd/initialize))
|
||||
(fn []
|
||||
(st/emit! (dd/finalize {:id team-id}))))
|
||||
(st/emit! (dd/finalize))))
|
||||
|
||||
(mf/with-effect []
|
||||
(let [key (events/listen goog/global "keydown"
|
||||
|
@ -257,31 +238,30 @@
|
|||
|
||||
(use-plugin-register plugin-url team-id (:id default-project))
|
||||
|
||||
[:& (mf/provider ctx/current-team-id) {:value team-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
|
||||
;; that the team is a implicit context variable that is
|
||||
;; available using react context or accessing
|
||||
;; the :current-team-id on the state. We set the key to the
|
||||
;; team-id because we want to completely refresh all the
|
||||
;; components on team change. Many components assumes that the
|
||||
;; team is already set so don't put the team into mf/deps.
|
||||
(when (and team initialized?)
|
||||
[:main {:class (stl/css :dashboard)
|
||||
:key (:id team)}
|
||||
[:& sidebar
|
||||
{:team team
|
||||
:projects projects
|
||||
:project project
|
||||
:profile profile
|
||||
:section section
|
||||
:search-term search-term}]
|
||||
(when (and team profile (seq projects))
|
||||
[:& dashboard-content
|
||||
{:projects projects
|
||||
:profile profile
|
||||
:project project
|
||||
:section section
|
||||
:search-term search-term
|
||||
:team team}])])]]]))
|
||||
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
||||
;; NOTE: dashboard events and other related functions assumes
|
||||
;; that the team is a implicit context variable that is
|
||||
;; available using react context or accessing
|
||||
;; the :current-team-id on the state. We set the key to the
|
||||
;; team-id because we want to completely refresh all the
|
||||
;; components on team change. Many components assumes that the
|
||||
;; team is already set so don't put the team into mf/deps.
|
||||
[:main {:class (stl/css :dashboard)
|
||||
:key (dm/str (:id team))}
|
||||
[:> sidebar*
|
||||
{:team team
|
||||
:projects projects
|
||||
:project project
|
||||
:default-project default-project
|
||||
:profile profile
|
||||
:section section
|
||||
:search-term search-term}]
|
||||
(when (seq projects)
|
||||
[:> dashboard-content*
|
||||
{:projects projects
|
||||
:profile profile
|
||||
:project project
|
||||
:default-project default-project
|
||||
:section section
|
||||
:search-term search-term
|
||||
:team team}])]]))
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.icons :as i]
|
||||
|
@ -25,8 +24,7 @@
|
|||
::mf/register-as :leave-and-reassign}
|
||||
[{:keys [profile team accept]}]
|
||||
(let [form (fm/use-form :schema schema:leave-modal-form :initial {})
|
||||
members-map (mf/deref refs/dashboard-team-members)
|
||||
members (vals members-map)
|
||||
members (get team :members)
|
||||
|
||||
options
|
||||
(into [{:value ""
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.workspace.comments :as dwcm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
|
@ -63,7 +63,7 @@
|
|||
(mf/defc comments-section
|
||||
[{:keys [profile team show? on-hide-comments]}]
|
||||
(let [threads-map (mf/deref refs/comment-threads)
|
||||
users (mf/deref refs/current-team-comments-users)
|
||||
profiles (mf/deref refs/profiles)
|
||||
team-id (:id team)
|
||||
|
||||
tgroups (->> (vals threads-map)
|
||||
|
@ -114,13 +114,13 @@
|
|||
{:group (first tgroups)
|
||||
:on-thread-click on-navigate
|
||||
:show-file-name true
|
||||
:users users}]
|
||||
:profiles profiles}]
|
||||
(for [tgroup (rest tgroups)]
|
||||
[:& cmt/comment-thread-group
|
||||
{:group tgroup
|
||||
:on-thread-click on-navigate
|
||||
:show-file-name true
|
||||
:users users
|
||||
:profiles profiles
|
||||
:key (:page-id tgroup)}])]
|
||||
|
||||
[:div {:class (stl/css :thread-groups-placeholder)}
|
||||
|
|
|
@ -9,18 +9,18 @@
|
|||
[app.config :as cf]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as-alias ev]
|
||||
[app.main.data.event :as-alias ev]
|
||||
[app.main.data.exports.files :as fexp]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -85,10 +85,9 @@
|
|||
|
||||
on-new-tab
|
||||
(fn [_]
|
||||
(let [path-params {:project-id (:project-id file)
|
||||
:file-id (:id file)}]
|
||||
(st/emit! (rt/nav-new-window* {:rname :workspace
|
||||
:path-params path-params}))))
|
||||
(st/emit! (dcm/go-to-workspace
|
||||
{:file-id (:id file)
|
||||
::rt/new-window true})))
|
||||
|
||||
on-duplicate
|
||||
(fn [_]
|
||||
|
@ -134,7 +133,9 @@
|
|||
(st/emit! (ntf/success (tr "dashboard.success-move-files")))
|
||||
(st/emit! (ntf/success (tr "dashboard.success-move-file"))))
|
||||
(if (or navigate (not= team-id current-team-id))
|
||||
(st/emit! (dd/go-to-files team-id project-id))
|
||||
(st/emit! (dcm/go-to-dashboard-files
|
||||
{:project-id project-id
|
||||
:team-id team-id}))
|
||||
(st/emit! (dd/fetch-recent-files)
|
||||
(dd/clear-selected-files))))
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
(ns app.main.ui.dashboard.files
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.project :as dpj]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.dashboard.grid :refer [grid]]
|
||||
|
@ -21,7 +23,6 @@
|
|||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.router :as rt]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -32,9 +33,12 @@
|
|||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [project create-fn can-edit]}]
|
||||
(let [local (mf/use-state
|
||||
{:menu-open false
|
||||
:edition false})
|
||||
(let [project-id (:id project)
|
||||
|
||||
local
|
||||
(mf/use-state
|
||||
{:menu-open false
|
||||
:edition false})
|
||||
|
||||
on-create-click
|
||||
(mf/use-fn
|
||||
|
@ -63,9 +67,9 @@
|
|||
|
||||
on-import
|
||||
(mf/use-fn
|
||||
(mf/deps (:id project))
|
||||
(mf/deps project-id)
|
||||
(fn []
|
||||
(st/emit! (dd/fetch-files {:project-id (:id project)})
|
||||
(st/emit! (dpj/fetch-files project-id)
|
||||
(dd/clear-selected-files))))]
|
||||
|
||||
|
||||
|
@ -126,32 +130,37 @@
|
|||
:on-menu-close on-menu-close
|
||||
:on-import on-import}])]]))
|
||||
|
||||
(mf/defc files-section
|
||||
(mf/defc files-section*
|
||||
{::mf/props :obj}
|
||||
[{:keys [project team]}]
|
||||
(let [files-map (mf/deref refs/dashboard-files)
|
||||
can-edit? (-> team :permissions :can-edit)
|
||||
project-id (:id project)
|
||||
is-draft-proyect (:is-default project)
|
||||
(let [files (mf/deref refs/files)
|
||||
project-id (get project :id)
|
||||
|
||||
[rowref limit] (hooks/use-dynamic-grid-item-width)
|
||||
files (mf/with-memo [project-id files]
|
||||
(->> (vals files)
|
||||
(filter #(= project-id (:project-id %)))
|
||||
(sort-by :modified-at)
|
||||
(reverse)))
|
||||
|
||||
files (mf/with-memo [project-id files-map]
|
||||
(->> (vals files-map)
|
||||
(filter #(= project-id (:project-id %)))
|
||||
(sort-by :modified-at)
|
||||
(reverse)))
|
||||
file-count (or (count files) 0)
|
||||
|
||||
can-edit? (-> team :permissions :can-edit)
|
||||
project-id (:id project)
|
||||
is-draft-proyect (:is-default project)
|
||||
|
||||
[rowref limit] (hooks/use-dynamic-grid-item-width)
|
||||
|
||||
file-count (or (count files) 0)
|
||||
empty-state-viewer (and (not can-edit?)
|
||||
(= 0 file-count))
|
||||
|
||||
selected-files (mf/deref refs/selected-files)
|
||||
|
||||
on-file-created
|
||||
(mf/use-fn
|
||||
(fn [data]
|
||||
(let [pparams {:project-id (:project-id data)
|
||||
:file-id (:id data)}
|
||||
qparams {:page-id (get-in data [:data :pages 0])}]
|
||||
(st/emit! (rt/nav :workspace pparams qparams)))))
|
||||
(fn [file-data]
|
||||
(let [file-id (:id file-data)
|
||||
page-id (get-in file-data [:pages 0])]
|
||||
(st/emit! (dcm/go-to-workspace :file-id file-id :page-id page-id)))))
|
||||
|
||||
create-file
|
||||
(mf/use-fn
|
||||
|
@ -170,7 +179,7 @@
|
|||
(dom/set-html-title (tr "title.dashboard.files" pname)))))
|
||||
|
||||
(mf/with-effect [project-id]
|
||||
(st/emit! (dd/fetch-files {:project-id project-id})
|
||||
(st/emit! (dpj/fetch-files project-id)
|
||||
(dd/clear-selected-files)))
|
||||
|
||||
[:*
|
||||
|
@ -191,6 +200,7 @@
|
|||
(tr "dashboard.empty-placeholder-files-subtitle"))}]
|
||||
[:& grid {:project project
|
||||
:files files
|
||||
:selected-files selected-files
|
||||
:can-edit can-edit?
|
||||
:origin :files
|
||||
:create-fn create-file
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
[app.common.media :as cm]
|
||||
[app.main.data.fonts :as df]
|
||||
[app.main.data.modal :as modal]
|
||||
[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*]]
|
||||
|
@ -24,6 +23,7 @@
|
|||
[app.util.keyboard :as kbd]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- use-page-title
|
||||
|
@ -42,7 +42,7 @@
|
|||
(and (contains? font :font-family-tmp)
|
||||
(str/blank? (:font-family-tmp font))))
|
||||
|
||||
(mf/defc header
|
||||
(mf/defc header*
|
||||
{::mf/props :obj
|
||||
::mf/memo true
|
||||
::mf/private true}
|
||||
|
@ -52,7 +52,7 @@
|
|||
[:div#dashboard-fonts-title {:class (stl/css :dashboard-title)}
|
||||
[:h1 (tr "labels.fonts")]]])
|
||||
|
||||
(mf/defc font-variant-display-name
|
||||
(mf/defc font-variant-display-name*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [variant]}]
|
||||
|
@ -61,10 +61,10 @@
|
|||
(when (not= "normal" (:font-style variant))
|
||||
[:span " " (str/capital (:font-style variant))])])
|
||||
|
||||
(mf/defc uploaded-fonts
|
||||
(mf/defc uploaded-fonts*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [team installed-fonts] :as props}]
|
||||
[{:keys [team installed-fonts]}]
|
||||
(let [fonts* (mf/use-state {})
|
||||
fonts (deref fonts*)
|
||||
font-vals (mf/with-memo [fonts]
|
||||
|
@ -219,7 +219,7 @@
|
|||
:default-value (:font-family item)}]]
|
||||
[:div {:class (stl/css :table-field :variants)}
|
||||
[:span {:class (stl/css :label)}
|
||||
[:& font-variant-display-name {:variant item}]]]
|
||||
[:> font-variant-display-name* {:variant item}]]]
|
||||
|
||||
[:div {:class (stl/css :table-field :filenames)}
|
||||
(for [item (:names item)]
|
||||
|
@ -364,7 +364,7 @@
|
|||
:inhert-variant (not can-edit))
|
||||
:key (dm/str id)}
|
||||
[:span {:class (stl/css :label)}
|
||||
[:& font-variant-display-name {:variant item}]]
|
||||
[:> font-variant-display-name* {:variant item}]]
|
||||
(when can-edit
|
||||
[:span
|
||||
{:class (stl/css :icon :close)
|
||||
|
@ -396,8 +396,9 @@
|
|||
:on-delete on-delete-font
|
||||
:on-edit on-edit}]]))]))
|
||||
|
||||
(mf/defc installed-fonts
|
||||
[{:keys [fonts can-edit] :as props}]
|
||||
(mf/defc installed-fonts*
|
||||
{::mf/props :obj}
|
||||
[{:keys [fonts can-edit]}]
|
||||
(let [sterm (mf/use-state "")
|
||||
|
||||
matches?
|
||||
|
@ -445,26 +446,27 @@
|
|||
:subtitle (tr "dashboard.fonts.empty-placeholder-viewer-sub")
|
||||
:type 2}]))]))
|
||||
|
||||
(def ^:private ref:fonts
|
||||
(l/derived :fonts st/state))
|
||||
|
||||
(mf/defc fonts-page
|
||||
(mf/defc fonts-page*
|
||||
{::mf/props :obj}
|
||||
[{:keys [team]}]
|
||||
(let [fonts (mf/deref refs/dashboard-fonts)
|
||||
(let [fonts (mf/deref ref: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)}
|
||||
(when ^boolean can-edit
|
||||
[:& uploaded-fonts {:team team :installed-fonts fonts}])
|
||||
[:& installed-fonts {:team team :fonts fonts :can-edit can-edit}]]]))
|
||||
[:> uploaded-fonts* {:team team :installed-fonts fonts}])
|
||||
[:> installed-fonts*
|
||||
{:team team :fonts fonts :can-edit can-edit}]]]))
|
||||
|
||||
(mf/defc font-providers-page
|
||||
(mf/defc font-providers-page*
|
||||
{::mf/props :obj}
|
||||
[{:keys [team]}]
|
||||
[:*
|
||||
[:& header {:team team :section :providers}]
|
||||
[:> header* {:team team :section :providers}]
|
||||
[:section {:class (stl/css :dashboard-container)}
|
||||
[:span "font providers"]]])
|
||||
|
||||
|
||||
|
|
|
@ -12,8 +12,11 @@
|
|||
[app.common.geom.point :as gpt]
|
||||
[app.common.logging :as log]
|
||||
[app.config :as cf]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.project :as dpj]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.features :as features]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.rasterizer :as thr]
|
||||
|
@ -71,11 +74,18 @@
|
|||
(rx/mapcat thr/render)
|
||||
(rx/mapcat (partial persist-thumbnail file-id revn))))
|
||||
|
||||
(mf/defc grid-item-thumbnail
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [file-id revn thumbnail-id background-color can-edit]}]
|
||||
(let [container (mf/use-ref)
|
||||
visible? (h/use-visible container :once? true)]
|
||||
(mf/defc grid-item-thumbnail*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [can-edit file]}]
|
||||
(let [file-id (get file :id)
|
||||
revn (get file :revn)
|
||||
thumbnail-id (get file :thumbnail-id)
|
||||
|
||||
bg-color (dm/get-in file [:data :background])
|
||||
|
||||
container (mf/use-ref)
|
||||
visible? (h/use-visible container :once? true)]
|
||||
|
||||
(mf/with-effect [file-id revn visible? thumbnail-id]
|
||||
(when (and visible? (not thumbnail-id))
|
||||
|
@ -89,7 +99,7 @@
|
|||
:message (ex-message cause)))))))
|
||||
|
||||
[:div {:class (stl/css :grid-item-th)
|
||||
:style {:background-color background-color}
|
||||
:style {:background-color bg-color}
|
||||
:ref container}
|
||||
(when visible?
|
||||
(if thumbnail-id
|
||||
|
@ -108,10 +118,9 @@
|
|||
(def ^:private menu-icon
|
||||
(i/icon-xref :menu (stl/css :menu-icon)))
|
||||
|
||||
(mf/defc grid-item-library
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [file] :as props}]
|
||||
|
||||
(mf/defc grid-item-library*
|
||||
{::mf/props :obj}
|
||||
[{:keys [file]}]
|
||||
(mf/with-effect [file]
|
||||
(when file
|
||||
(let [font-ids (map :font-id (get-in file [:library-summary :typographies :sample] []))]
|
||||
|
@ -231,16 +240,12 @@
|
|||
(dom/set-text! counter-el (str file-count))
|
||||
counter-el))
|
||||
|
||||
(mf/defc grid-item
|
||||
{:wrap [mf/memo]}
|
||||
[{:keys [file origin library-view? can-edit] :as props}]
|
||||
(mf/defc grid-item*
|
||||
{::mf/props :obj}
|
||||
[{:keys [file origin can-edit selected-files]}]
|
||||
(let [file-id (:id file)
|
||||
|
||||
;; FIXME: this breaks react hooks rule, hooks should never to
|
||||
;; be in a conditional code
|
||||
selected-files (if (= origin :search)
|
||||
(mf/deref refs/dashboard-selected-search)
|
||||
(mf/deref refs/dashboard-selected-files))
|
||||
is-library-view (= origin :libraries)
|
||||
|
||||
dashboard-local (mf/deref refs/dashboard-local)
|
||||
file-menu-open? (:menu-open dashboard-local)
|
||||
|
@ -267,12 +272,12 @@
|
|||
|
||||
on-navigate
|
||||
(mf/use-fn
|
||||
(mf/deps file)
|
||||
(mf/deps file-id)
|
||||
(fn [event]
|
||||
(let [menu-icon (mf/ref-val menu-ref)
|
||||
target (dom/get-target event)]
|
||||
(when-not (dom/child? target menu-icon)
|
||||
(st/emit! (dd/go-to-workspace file))))))
|
||||
(st/emit! (dcm/go-to-workspace :file-id file-id))))))
|
||||
|
||||
on-drag-start
|
||||
(mf/use-fn
|
||||
|
@ -354,9 +359,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 is-library-view)}
|
||||
[:div
|
||||
{:class (stl/css-case :selected selected? :library library-view?)
|
||||
{:class (stl/css-case :selected selected?
|
||||
:library is-library-view)
|
||||
:ref node-ref
|
||||
:role "button"
|
||||
:title (:name file)
|
||||
|
@ -369,16 +377,11 @@
|
|||
|
||||
[:div {:class (stl/css :overlay)}]
|
||||
|
||||
(if library-view?
|
||||
[:& grid-item-library {:file file}]
|
||||
[:& grid-item-thumbnail
|
||||
{:file-id (:id file)
|
||||
:can-edit can-edit
|
||||
:revn (:revn file)
|
||||
:thumbnail-id (:thumbnail-id file)
|
||||
:background-color (dm/get-in file [:data :options :background])}])
|
||||
(if ^boolean is-library-view
|
||||
[:> grid-item-library* {:file file}]
|
||||
[:> grid-item-thumbnail* {:file file :can-edit can-edit}])
|
||||
|
||||
(when (and (:is-shared file) (not library-view?))
|
||||
(when (and (:is-shared file) (not is-library-view))
|
||||
[:div {:class (stl/css :item-badge)} i/library])
|
||||
|
||||
[:div {:class (stl/css :info-wrapper)}
|
||||
|
@ -417,7 +420,8 @@
|
|||
:parent-id (dm/str file-id "-action-menu")}]])]]]]]))
|
||||
|
||||
(mf/defc grid
|
||||
[{:keys [files project origin limit library-view? create-fn can-edit] :as props}]
|
||||
{::mf/props :obj}
|
||||
[{:keys [files project origin limit create-fn can-edit selected-files]}]
|
||||
(let [dragging? (mf/use-state false)
|
||||
project-id (:id project)
|
||||
node-ref (mf/use-var nil)
|
||||
|
@ -425,10 +429,12 @@
|
|||
on-finish-import
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(st/emit! (dd/fetch-files {:project-id project-id})
|
||||
(dd/fetch-shared-files)
|
||||
(st/emit! (dpj/fetch-files project-id)
|
||||
(dtm/fetch-shared-files)
|
||||
(dd/clear-selected-files))))
|
||||
|
||||
|
||||
|
||||
import-files (use-import-file project-id on-finish-import)
|
||||
|
||||
on-drag-enter
|
||||
|
@ -484,13 +490,12 @@
|
|||
(when @dragging?
|
||||
[:li {:class (stl/css :grid-item)}])
|
||||
(for [item slice]
|
||||
[:& grid-item
|
||||
[:> grid-item*
|
||||
{:file item
|
||||
:key (:id item)
|
||||
:navigate? true
|
||||
:key (dm/str (:id item))
|
||||
:origin origin
|
||||
:can-edit can-edit
|
||||
:library-view? library-view?}])])
|
||||
:selected-files selected-files
|
||||
:can-edit can-edit}])])
|
||||
|
||||
:else
|
||||
[:& empty-placeholder
|
||||
|
@ -510,13 +515,12 @@
|
|||
[:li {:class (stl/css :grid-item :dragged)}])
|
||||
|
||||
(for [item (take limit files)]
|
||||
[:& grid-item
|
||||
[:> grid-item*
|
||||
{:id (:id item)
|
||||
:file item
|
||||
:selected-files selected-files
|
||||
:can-edit can-edit
|
||||
:key (:id item)
|
||||
:navigate? false}])]))
|
||||
:key (dm/str (:id item))}])]))
|
||||
|
||||
(mf/defc line-grid
|
||||
[{:keys [project team files limit create-fn can-edit] :as props}]
|
||||
|
@ -524,8 +528,8 @@
|
|||
project-id (:id project)
|
||||
team-id (:id team)
|
||||
|
||||
selected-files (mf/deref refs/dashboard-selected-files)
|
||||
selected-project (mf/deref refs/dashboard-selected-project)
|
||||
selected-files (mf/deref refs/selected-files)
|
||||
selected-project (mf/deref refs/selected-project)
|
||||
|
||||
on-finish-import
|
||||
(mf/use-fn
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.logging :as log]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.errors :as errors]
|
||||
|
|
|
@ -7,9 +7,8 @@
|
|||
(ns app.main.ui.dashboard.libraries
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.features :as features]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.dashboard.grid :refer [grid]]
|
||||
|
@ -18,35 +17,32 @@
|
|||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc libraries-page
|
||||
(mf/defc libraries-page*
|
||||
{::mf/props :obj}
|
||||
[{:keys [team] :as props}]
|
||||
(let [files-map (mf/deref refs/dashboard-shared-files)
|
||||
projects (mf/deref refs/dashboard-projects)
|
||||
can-edit (-> team :permissions :can-edit)
|
||||
[{:keys [team default-project]}]
|
||||
(let [files
|
||||
(mf/deref refs/shared-files)
|
||||
|
||||
default-project (->> projects vals (d/seek :is-default))
|
||||
files
|
||||
(mf/with-memo [files]
|
||||
(->> (vals files)
|
||||
(sort-by :modified-at)
|
||||
(reverse)))
|
||||
|
||||
files (mf/with-memo [files-map]
|
||||
(if (nil? files-map)
|
||||
nil
|
||||
(->> (vals files-map)
|
||||
(sort-by :modified-at)
|
||||
(reverse))))
|
||||
can-edit
|
||||
(-> team :permissions :can-edit)
|
||||
|
||||
components-v2 (features/use-feature "components/v2")
|
||||
|
||||
[rowref limit] (hooks/use-dynamic-grid-item-width 350)]
|
||||
[rowref limit]
|
||||
(hooks/use-dynamic-grid-item-width 350)]
|
||||
|
||||
(mf/with-effect [team]
|
||||
(when team
|
||||
(let [tname (if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))]
|
||||
(dom/set-html-title (tr "title.dashboard.shared-libraries" tname)))))
|
||||
(let [tname (if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))]
|
||||
(dom/set-html-title (tr "title.dashboard.shared-libraries" tname))))
|
||||
|
||||
(mf/with-effect []
|
||||
(st/emit! (dd/fetch-shared-files (:id team))
|
||||
(mf/with-effect [team]
|
||||
(st/emit! (dtm/fetch-shared-files)
|
||||
(dd/clear-selected-files)))
|
||||
|
||||
[:*
|
||||
|
@ -58,6 +54,5 @@
|
|||
:project default-project
|
||||
:origin :libraries
|
||||
:limit limit
|
||||
:can-edit can-edit
|
||||
:library-view? components-v2}]]]))
|
||||
:can-edit can-edit}]]]))
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.main.ui.dashboard.project-menu
|
||||
(:require
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
|
@ -16,7 +17,6 @@
|
|||
[app.main.ui.dashboard.import :as udi]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc project-menu*
|
||||
|
@ -32,9 +32,9 @@
|
|||
on-duplicate-success
|
||||
(fn [new-project]
|
||||
(st/emit! (ntf/success (tr "dashboard.success-duplicate-project"))
|
||||
(rt/nav :dashboard-files
|
||||
{:team-id (:team-id new-project)
|
||||
:project-id (:id new-project)})))
|
||||
(dcm/go-to-dashboard-files
|
||||
:team-id (:team-id new-project)
|
||||
:project-id (:id new-project))))
|
||||
|
||||
on-duplicate
|
||||
(fn []
|
||||
|
@ -46,7 +46,7 @@
|
|||
|
||||
on-move-success
|
||||
(fn [team-id]
|
||||
(st/emit! (dd/go-to-projects team-id)))
|
||||
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))
|
||||
|
||||
on-move
|
||||
(fn [team-id]
|
||||
|
@ -57,9 +57,10 @@
|
|||
|
||||
delete-fn
|
||||
(fn [_]
|
||||
(st/emit! (ntf/success (tr "dashboard.success-delete-project"))
|
||||
(dd/delete-project project)
|
||||
(dd/go-to-projects (:team-id project))))
|
||||
(let [team-id (:team-id project)]
|
||||
(st/emit! (ntf/success (tr "dashboard.success-delete-project"))
|
||||
(dd/delete-project project)
|
||||
(dcm/go-to-dashboard-recent :team-id team-id))))
|
||||
|
||||
on-delete
|
||||
#(st/emit!
|
||||
|
|
|
@ -8,9 +8,11 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.project :as dpj]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.dashboard.grid :refer [line-grid]]
|
||||
|
@ -23,7 +25,6 @@
|
|||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :as storage]
|
||||
[app.util.time :as dt]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -43,8 +44,10 @@
|
|||
(def ^:private menu-icon
|
||||
(i/icon-xref :menu (stl/css :menu-icon)))
|
||||
|
||||
(mf/defc header
|
||||
{::mf/wrap [mf/memo]}
|
||||
(mf/defc header*
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [can-edit]}]
|
||||
(let [on-click (mf/use-fn #(st/emit! (dd/create-project)))]
|
||||
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
|
||||
|
@ -60,7 +63,7 @@
|
|||
{::mf/wrap [mf/memo]
|
||||
::mf/props :obj}
|
||||
[{:keys [team on-close]}]
|
||||
(let [on-nav-members-click (mf/use-fn #(st/emit! (dd/go-to-team-members)))
|
||||
(let [on-nav-members-click (mf/use-fn #(st/emit! (dcm/go-to-dashboard-members)))
|
||||
|
||||
on-invite
|
||||
(mf/use-fn
|
||||
|
@ -96,34 +99,35 @@
|
|||
:aria-label (tr "labels.close")}
|
||||
close-icon]]))
|
||||
|
||||
(def builtin-templates
|
||||
(l/derived :builtin-templates st/state))
|
||||
|
||||
(mf/defc project-item
|
||||
[{:keys [project first? team files can-edit] :as props}]
|
||||
(mf/defc project-item*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [project is-first team files can-edit]}]
|
||||
(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 (not can-edit)
|
||||
(= 0 file-count))
|
||||
|
||||
file-count (or (:count project) 0)
|
||||
is-draft? (:is-default project)
|
||||
empty? (and (not can-edit)
|
||||
(= 0 file-count))
|
||||
|
||||
dstate (mf/deref refs/dashboard-local)
|
||||
edit-id (:project-for-edit dstate)
|
||||
|
||||
local (mf/use-state {:menu-open false
|
||||
:menu-pos nil
|
||||
:edition? (= (:id project) edit-id)})
|
||||
:edition (= (:id project) edit-id)})
|
||||
|
||||
[rowref limit]
|
||||
(hooks/use-dynamic-grid-item-width)
|
||||
|
||||
[rowref limit] (hooks/use-dynamic-grid-item-width)
|
||||
on-nav
|
||||
(mf/use-fn
|
||||
(mf/deps project-id team-id)
|
||||
(mf/deps project-id)
|
||||
(fn []
|
||||
(st/emit! (rt/nav :dashboard-files
|
||||
{:team-id team-id
|
||||
:project-id project-id}))))
|
||||
(st/emit! (dcm/go-to-dashboard-files :project-id project-id))))
|
||||
|
||||
toggle-pin
|
||||
(mf/use-fn
|
||||
(mf/deps project)
|
||||
|
@ -152,7 +156,7 @@
|
|||
(mf/use-fn #(swap! local assoc :menu-open false))
|
||||
|
||||
on-edit-open
|
||||
(mf/use-fn #(swap! local assoc :edition? true))
|
||||
(mf/use-fn #(swap! local assoc :edition true))
|
||||
|
||||
on-edit
|
||||
(mf/use-fn
|
||||
|
@ -162,15 +166,13 @@
|
|||
(when-not (str/empty? name)
|
||||
(st/emit! (-> (dd/rename-project (assoc project :name name))
|
||||
(with-meta {::ev/origin "dashboard"}))))
|
||||
(swap! local assoc :edition? false))))
|
||||
(swap! local assoc :edition false))))
|
||||
|
||||
on-file-created
|
||||
(mf/use-fn
|
||||
(fn [data]
|
||||
(let [pparams {:project-id (:project-id data)
|
||||
:file-id (:id data)}
|
||||
qparams {:page-id (get-in data [:data :pages 0])}]
|
||||
(st/emit! (rt/nav :workspace pparams qparams)))))
|
||||
(fn [{:keys [id data]}]
|
||||
(let [page-id (get-in data [:pages 0])]
|
||||
(st/emit! (dcm/go-to-workspace :file-id id :page-id page-id)))))
|
||||
|
||||
create-file
|
||||
(mf/use-fn
|
||||
|
@ -189,9 +191,9 @@
|
|||
|
||||
on-import
|
||||
(mf/use-fn
|
||||
(mf/deps project-id (:id team))
|
||||
(mf/deps project-id)
|
||||
(fn []
|
||||
(st/emit! (dd/fetch-files {:project-id project-id})
|
||||
(st/emit! (dpj/fetch-files project-id)
|
||||
(dd/fetch-recent-files)
|
||||
(dd/fetch-projects)
|
||||
(dd/clear-selected-files))))
|
||||
|
@ -212,10 +214,10 @@
|
|||
(on-menu-click event))))
|
||||
title-width (/ 100 limit)]
|
||||
|
||||
[:article {:class (stl/css-case :dashboard-project-row true :first first?)}
|
||||
[:article {:class (stl/css-case :dashboard-project-row true :first is-first)}
|
||||
[:header {:class (stl/css :project)}
|
||||
[:div {:class (stl/css :project-name-wrapper)}
|
||||
(if (:edition? @local)
|
||||
(if (:edition @local)
|
||||
[:& inline-edition {:content (:name project)
|
||||
:on-end on-edit}]
|
||||
[:h2 {:on-click on-nav
|
||||
|
@ -231,7 +233,6 @@
|
|||
|
||||
[:div {:class (stl/css :info-wrapper)}
|
||||
|
||||
|
||||
;; We group these two spans under a div to avoid having extra space between them.
|
||||
[:div
|
||||
[:span {:class (stl/css :info)} (str (tr "labels.num-of-files" (i18n/c file-count)))]
|
||||
|
@ -274,13 +275,13 @@
|
|||
:on-import on-import}])]]]
|
||||
|
||||
[:div {:class (stl/css :grid-container) :ref rowref}
|
||||
(if empty-state-viewer
|
||||
[:> empty-placeholder* {:title (if is-draft-proyect
|
||||
(if ^boolean empty?
|
||||
[:> empty-placeholder* {:title (if ^boolean is-draft?
|
||||
(tr "dashboard.empty-placeholder-drafts-title")
|
||||
(tr "dashboard.empty-placeholder-files-title"))
|
||||
:class (stl/css :placeholder-placement)
|
||||
:type 1
|
||||
:subtitle (if is-draft-proyect
|
||||
:subtitle (if ^boolean is-draft?
|
||||
(tr "dashboard.empty-placeholder-drafts-subtitle")
|
||||
(tr "dashboard.empty-placeholder-files-subtitle"))}]
|
||||
|
||||
|
@ -303,16 +304,19 @@
|
|||
[:span {:class (stl/css :placeholder-label)} (tr "dashboard.show-all-files")]
|
||||
show-more-icon])]))
|
||||
|
||||
(def ref:recent-files
|
||||
(l/derived :dashboard-recent-files st/state))
|
||||
(def ^:private ref:recent-files
|
||||
(l/derived :recent-files st/state))
|
||||
|
||||
(mf/defc projects-section
|
||||
(mf/defc projects-section*
|
||||
{::mf/props :obj}
|
||||
[{:keys [team projects profile]}]
|
||||
|
||||
(let [projects (->> (vals projects)
|
||||
(sort-by :modified-at)
|
||||
(reverse))
|
||||
(let [projects
|
||||
(mf/with-memo [projects]
|
||||
(->> projects
|
||||
(sort-by :modified-at)
|
||||
(reverse)))
|
||||
|
||||
recent-map (mf/deref ref:recent-files)
|
||||
permisions (:permissions team)
|
||||
|
||||
|
@ -334,7 +338,7 @@
|
|||
::ev/origin "dashboard"}))))]
|
||||
|
||||
(mf/with-effect [show-team-hero?]
|
||||
(swap! storage/global assoc ::show-team-hero show-team-hero?))
|
||||
(swap! storage/global assoc ::show-eam-hero show-team-hero?))
|
||||
|
||||
(mf/with-effect [team]
|
||||
(let [tname (if (:is-default team)
|
||||
|
@ -348,7 +352,7 @@
|
|||
|
||||
(when (seq projects)
|
||||
[:*
|
||||
[:& header {:can-edit can-edit}]
|
||||
[:> header* {:can-edit can-edit}]
|
||||
[:div {:class (stl/css :projects-container)}
|
||||
[:*
|
||||
(when (and show-team-hero?
|
||||
|
@ -368,9 +372,9 @@
|
|||
(->> (vals recent-map)
|
||||
(filterv #(= id (:project-id %)))
|
||||
(sort-by :modified-at #(compare %2 %1))))]
|
||||
[:& project-item {:project project
|
||||
:team team
|
||||
:files files
|
||||
:can-edit can-edit
|
||||
:first? (= project (first projects))
|
||||
:key id}]))]]]])))
|
||||
[:> project-item* {:project project
|
||||
:team team
|
||||
:files files
|
||||
:can-edit can-edit
|
||||
:is-first (= project (first projects))
|
||||
:key id}]))]]]])))
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.main.ui.dashboard.search
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
|
@ -15,28 +16,43 @@
|
|||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc search-page
|
||||
[{:keys [team search-term] :as props}]
|
||||
(let [search-term (or search-term "")
|
||||
result (mf/deref refs/dashboard-search-result)
|
||||
[rowref limit] (hooks/use-dynamic-grid-item-width)]
|
||||
(def ^:private ref:search-result
|
||||
(l/derived :search-result st/state))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps team)
|
||||
(fn []
|
||||
(when team
|
||||
(let [tname (if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))]
|
||||
(dom/set-html-title (tr "title.dashboard.search" tname))))))
|
||||
(def ^:private ref:selected
|
||||
(l/derived (fn [state]
|
||||
;; we need to this because :dashboard-search-result is a list
|
||||
;; of maps and we need a map of maps (using :id as key).
|
||||
(let [files (d/index-by :id (:search-result state))]
|
||||
(->> (get state :selected-files)
|
||||
(refs/extract-selected-files files))))
|
||||
st/state))
|
||||
|
||||
(mf/defc search-page*
|
||||
{::mf/props :obj}
|
||||
[{:keys [team search-term]}]
|
||||
(let [search-term (d/nilv search-term "")
|
||||
|
||||
result (mf/deref ref:search-result)
|
||||
selected (mf/deref ref:selected)
|
||||
|
||||
[rowref limit]
|
||||
(hooks/use-dynamic-grid-item-width)]
|
||||
|
||||
(mf/with-effect [team]
|
||||
(when team
|
||||
(let [tname (if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team))]
|
||||
(dom/set-html-title (tr "title.dashboard.search" tname)))))
|
||||
|
||||
(mf/with-effect [search-term]
|
||||
(st/emit! (dd/search {:search-term search-term})
|
||||
(dd/clear-selected-files)))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps search-term)
|
||||
(fn []
|
||||
(st/emit! (dd/search {:search-term search-term})
|
||||
(dd/clear-selected-files))))
|
||||
[:*
|
||||
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
|
||||
[:div#dashboard-search-title {:class (stl/css :dashboard-title)}
|
||||
|
@ -62,6 +78,6 @@
|
|||
|
||||
:else
|
||||
[:& grid {:files result
|
||||
:hide-new? true
|
||||
:selected-files selected
|
||||
:origin :search
|
||||
:limit limit}])]]))
|
||||
|
|
|
@ -7,16 +7,18 @@
|
|||
(ns app.main.ui.dashboard.sidebar
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[app.main.data.auth :as da]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown-menu :refer [dropdown-menu dropdown-menu-item*]]
|
||||
[app.main.ui.components.link :refer [link]]
|
||||
|
@ -29,8 +31,6 @@
|
|||
[app.util.dom.dnd :as dnd]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.object :as obj]
|
||||
[app.util.router :as rt]
|
||||
[app.util.timers :as ts]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
|
@ -74,31 +74,34 @@
|
|||
edit-id (:project-for-edit dstate)
|
||||
|
||||
local* (mf/use-state
|
||||
{:menu-open false
|
||||
:menu-pos nil
|
||||
:edition? (= (:id item) edit-id)
|
||||
:dragging? false})
|
||||
#(do {:menu-open false
|
||||
:menu-pos nil
|
||||
:edition? (= (:id item) edit-id)
|
||||
:dragging? false}))
|
||||
|
||||
local (deref local*)
|
||||
|
||||
project-id (get item :id)
|
||||
|
||||
local @local*
|
||||
on-click
|
||||
(mf/use-fn
|
||||
(mf/deps item)
|
||||
(mf/deps project-id)
|
||||
(fn []
|
||||
(st/emit! (dd/go-to-files (:id item)))))
|
||||
(st/emit! (dcm/go-to-dashboard-files :project-id project-id))))
|
||||
|
||||
on-key-down
|
||||
(mf/use-fn
|
||||
(mf/deps item)
|
||||
(mf/deps project-id)
|
||||
(fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(st/emit! (dd/go-to-files (:id item))
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(let [project-title (dom/get-element (str (:id item)))]
|
||||
(when project-title
|
||||
(dom/set-attribute! project-title "tabindex" "0")
|
||||
(dom/focus! project-title)
|
||||
(dom/set-attribute! project-title "tabindex" "-1")))))))))
|
||||
(st/emit!
|
||||
(dcm/go-to-dashboard-files :project-id project-id)
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(when-let [title (dom/get-element (str project-id))]
|
||||
(dom/set-attribute! title "tabindex" "0")
|
||||
(dom/focus! title)
|
||||
(dom/set-attribute! title "tabindex" "-1"))))))))
|
||||
|
||||
on-menu-click
|
||||
(mf/use-fn
|
||||
|
@ -148,9 +151,10 @@
|
|||
|
||||
on-drop-success
|
||||
(mf/use-fn
|
||||
(mf/deps (:id item))
|
||||
#(st/emit! (ntf/success (tr "dashboard.success-move-file"))
|
||||
(dd/go-to-files (:id item))))
|
||||
(mf/deps project-id)
|
||||
(fn [_]
|
||||
(st/emit! (dcm/go-to-dashboard-files :project-id project-id)
|
||||
(ntf/success (tr "dashboard.success-move-file")))))
|
||||
|
||||
on-drop
|
||||
(mf/use-fn
|
||||
|
@ -201,19 +205,18 @@
|
|||
|
||||
on-search-change
|
||||
(mf/use-fn
|
||||
(mf/deps team-id)
|
||||
(fn [event]
|
||||
(let [value (dom/get-target-val event)]
|
||||
(emit! (dd/go-to-search value)))))
|
||||
(emit! (dcm/go-to-dashboard-search :term value)))))
|
||||
|
||||
on-clear-click
|
||||
(mf/use-fn
|
||||
(mf/deps team-id)
|
||||
(fn [e]
|
||||
(emit! (dcm/go-to-dashboard-search))
|
||||
(let [search-input (dom/get-element "search-input")]
|
||||
(dom/clean-value! search-input)
|
||||
(dom/focus! search-input)
|
||||
(emit! (dd/go-to-search))
|
||||
(dom/prevent-default e)
|
||||
(dom/stop-propagation e))))
|
||||
|
||||
|
@ -278,7 +281,8 @@
|
|||
(fn [event]
|
||||
(let [team-id (-> (dom/get-current-target event)
|
||||
(dom/get-data "value"))]
|
||||
(st/emit! (dd/go-to-projects team-id)))))
|
||||
|
||||
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))
|
||||
|
||||
handle-select-default
|
||||
(mf/use-fn
|
||||
|
@ -343,20 +347,22 @@
|
|||
|
||||
(mf/defc team-options-dropdown
|
||||
[{:keys [team profile] :as props}]
|
||||
(let [go-members #(st/emit! (dd/go-to-team-members))
|
||||
go-invitations #(st/emit! (dd/go-to-team-invitations))
|
||||
go-webhooks #(st/emit! (dd/go-to-team-webhooks))
|
||||
go-settings #(st/emit! (dd/go-to-team-settings))
|
||||
(let [go-members #(st/emit! (dcm/go-to-dashboard-members))
|
||||
go-invitations #(st/emit! (dcm/go-to-dashboard-invitations))
|
||||
go-webhooks #(st/emit! (dcm/go-to-dashboard-webhooks))
|
||||
go-settings #(st/emit! (dcm/go-to-dashboard-settings))
|
||||
|
||||
members-map (mf/deref refs/dashboard-team-members)
|
||||
members (vals members-map)
|
||||
can-rename? (or (get-in team [:permissions :is-owner]) (get-in team [:permissions :is-admin]))
|
||||
members (get team :members)
|
||||
permissions (get team :permissions)
|
||||
can-rename? (or (:is-owner permissions)
|
||||
(:is-admin permissions))
|
||||
|
||||
on-success
|
||||
(fn []
|
||||
(st/emit! (dd/go-to-projects (:default-team-id profile))
|
||||
(modal/hide)
|
||||
(du/fetch-teams)))
|
||||
;; FIXME: this should be handled in the event, not here
|
||||
(let [team-id (:default-team-id profile)]
|
||||
(rx/of (dcm/go-to-dashboard-recent :team-id team-id)
|
||||
(modal/hide))))
|
||||
|
||||
on-error
|
||||
(fn [{:keys [code] :as error}]
|
||||
|
@ -377,15 +383,15 @@
|
|||
(mf/deps on-success on-error)
|
||||
(fn [member-id]
|
||||
(let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))]
|
||||
(st/emit! (dd/leave-team (with-meta params
|
||||
{:on-success on-success
|
||||
:on-error on-error}))))))
|
||||
(st/emit! (dtm/leave-current-team (with-meta params
|
||||
{:on-success on-success
|
||||
:on-error on-error}))))))
|
||||
delete-fn
|
||||
(mf/use-fn
|
||||
(mf/deps team on-success on-error)
|
||||
(fn []
|
||||
(st/emit! (dd/delete-team (with-meta team {:on-success on-success
|
||||
:on-error on-error})))))
|
||||
(st/emit! (dtm/delete-team (with-meta team {:on-success on-success
|
||||
:on-error on-error})))))
|
||||
on-rename-clicked
|
||||
(mf/use-fn
|
||||
(mf/deps team)
|
||||
|
@ -406,7 +412,7 @@
|
|||
(mf/use-fn
|
||||
(mf/deps team profile leave-fn)
|
||||
(fn []
|
||||
(st/emit! (dd/fetch-team-members (:id team))
|
||||
(st/emit! (dtm/fetch-members)
|
||||
(modal/show
|
||||
{:type :leave-and-reassign
|
||||
:profile profile
|
||||
|
@ -590,6 +596,10 @@
|
|||
(when (get-in team [:permissions :is-owner])
|
||||
"teams-options-delete-team")]
|
||||
|
||||
|
||||
;; _ (prn "--------------- sidebar-team-switch")
|
||||
;; _ (app.common.pprint/pprint teams)
|
||||
|
||||
handle-show-team-click
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
|
@ -679,45 +689,45 @@
|
|||
[:& team-options-dropdown {:team team
|
||||
:profile profile}]]]))
|
||||
|
||||
(mf/defc sidebar-content
|
||||
[{:keys [projects profile section team project search-term] :as props}]
|
||||
(mf/defc sidebar-content*
|
||||
{::mf/private true
|
||||
::mf/props :obj}
|
||||
[{:keys [projects profile section team project search-term default-project] :as props}]
|
||||
(let [default-project-id
|
||||
(->> (vals projects)
|
||||
(d/seek :is-default)
|
||||
(:id))
|
||||
(get default-project :id)
|
||||
|
||||
projects? (= section :dashboard-projects)
|
||||
team-id (get team :id)
|
||||
|
||||
projects? (= section :dashboard-recent)
|
||||
fonts? (= section :dashboard-fonts)
|
||||
libs? (= section :dashboard-libraries)
|
||||
drafts? (and (= section :dashboard-files)
|
||||
(= (:id project) default-project-id))
|
||||
|
||||
go-projects
|
||||
(mf/use-fn
|
||||
(mf/deps team)
|
||||
#(st/emit! (rt/nav :dashboard-projects {:team-id (:id team)})))
|
||||
(mf/use-fn #(st/emit! (dcm/go-to-dashboard-recent)))
|
||||
|
||||
go-projects-with-key
|
||||
(mf/use-fn
|
||||
(mf/deps team)
|
||||
#(st/emit! (rt/nav :dashboard-projects {:team-id (:id team)})
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(let [projects-title (dom/get-element "dashboard-projects-title")]
|
||||
(when projects-title
|
||||
(dom/set-attribute! projects-title "tabindex" "0")
|
||||
(dom/focus! projects-title)
|
||||
(dom/set-attribute! projects-title "tabindex" "-1")))))))
|
||||
(mf/deps team-id)
|
||||
(fn []
|
||||
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(when-let [projects-title (dom/get-element "dashboard-projects-title")]
|
||||
(dom/set-attribute! projects-title "tabindex" "0")
|
||||
(dom/focus! projects-title)
|
||||
(dom/set-attribute! projects-title "tabindex" "-1")))))))
|
||||
|
||||
go-fonts
|
||||
(mf/use-fn
|
||||
(mf/deps team)
|
||||
#(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)})))
|
||||
(mf/deps team-id)
|
||||
#(st/emit! (dcm/go-to-dashboard-fonts :team-id team-id)))
|
||||
|
||||
go-fonts-with-key
|
||||
(mf/use-fn
|
||||
(mf/deps team)
|
||||
#(st/emit! (rt/nav :dashboard-fonts {:team-id (:id team)})
|
||||
#(st/emit! (dcm/go-to-dashboard-fonts :team-id team-id)
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(let [font-title (dom/get-element "dashboard-fonts-title")]
|
||||
|
@ -727,34 +737,31 @@
|
|||
(dom/set-attribute! font-title "tabindex" "-1")))))))
|
||||
go-drafts
|
||||
(mf/use-fn
|
||||
(mf/deps team default-project-id)
|
||||
(mf/deps team-id default-project-id)
|
||||
(fn []
|
||||
(st/emit! (rt/nav :dashboard-files
|
||||
{:team-id (:id team)
|
||||
:project-id default-project-id}))))
|
||||
(st/emit! (dcm/go-to-dashboard-files :team-id team-id :project-id default-project-id))))
|
||||
|
||||
go-drafts-with-key
|
||||
(mf/use-fn
|
||||
(mf/deps team default-project-id)
|
||||
#(st/emit! (rt/nav :dashboard-files {:team-id (:id team)
|
||||
:project-id default-project-id})
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(let [drafts-title (dom/get-element "dashboard-drafts-title")]
|
||||
(when drafts-title
|
||||
(dom/set-attribute! drafts-title "tabindex" "0")
|
||||
(dom/focus! drafts-title)
|
||||
(dom/set-attribute! drafts-title "tabindex" "-1")))))))
|
||||
(mf/deps team-id default-project-id)
|
||||
(fn []
|
||||
(st/emit! (dcm/go-to-dashboard-files :team-id team-id :project-id default-project-id))
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(when-let [title (dom/get-element "dashboard-drafts-title")]
|
||||
(dom/set-attribute! title "tabindex" "0")
|
||||
(dom/focus! title)
|
||||
(dom/set-attribute! title "tabindex" "-1"))))))
|
||||
|
||||
go-libs
|
||||
(mf/use-fn
|
||||
(mf/deps team)
|
||||
#(st/emit! (rt/nav :dashboard-libraries {:team-id (:id team)})))
|
||||
(mf/deps team-id)
|
||||
#(st/emit! (dcm/go-to-dashboard-libraries :team-id team-id)))
|
||||
|
||||
go-libs-with-key
|
||||
(mf/use-fn
|
||||
(mf/deps team)
|
||||
#(st/emit! (rt/nav :dashboard-libraries {:team-id (:id team)})
|
||||
(mf/deps team-id)
|
||||
#(st/emit! (dcm/go-to-dashboard-libraries :team-id team-id)
|
||||
(ts/schedule-on-idle
|
||||
(fn []
|
||||
(let [libs-title (dom/get-element "dashboard-libraries-title")]
|
||||
|
@ -763,7 +770,7 @@
|
|||
(dom/focus! libs-title)
|
||||
(dom/set-attribute! libs-title "tabindex" "-1")))))))
|
||||
pinned-projects
|
||||
(->> (vals projects)
|
||||
(->> projects
|
||||
(remove :is-default)
|
||||
(filter :is-pinned))]
|
||||
|
||||
|
@ -826,11 +833,12 @@
|
|||
pin-icon
|
||||
[:span {:class (stl/css :empty-text)} (tr "dashboard.no-projects-placeholder")]])]]))
|
||||
|
||||
(mf/defc profile-section
|
||||
[{:keys [profile team] :as props}]
|
||||
(mf/defc profile-section*
|
||||
{::mf/props :obj}
|
||||
[{:keys [profile team]}]
|
||||
(let [show* (mf/use-state false)
|
||||
show (deref show*)
|
||||
photo (cf/resolve-profile-photo-url profile)
|
||||
photo (cf/resolve-profile-photo-url profile)
|
||||
|
||||
on-click
|
||||
(mf/use-fn
|
||||
|
@ -875,14 +883,13 @@
|
|||
(when (kbd/enter? event)
|
||||
(reset! show* true))))
|
||||
|
||||
handle-close
|
||||
on-close
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(reset! show* false))
|
||||
|
||||
handle-key-down-profile
|
||||
(mf/use-fn
|
||||
(mf/deps on-click)
|
||||
(fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-click :settings-profile event))))
|
||||
|
@ -910,34 +917,27 @@
|
|||
(show-release-notes))))
|
||||
|
||||
handle-feedback-click
|
||||
(mf/use-fn
|
||||
(mf/deps on-click)
|
||||
#(on-click :settings-feedback %))
|
||||
(mf/use-fn #(on-click :settings-feedback %))
|
||||
|
||||
handle-feedback-keydown
|
||||
(mf/use-fn
|
||||
(mf/deps on-click)
|
||||
(fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-click :settings-feedback event))))
|
||||
|
||||
handle-logout-click
|
||||
(mf/use-fn
|
||||
(mf/deps on-click)
|
||||
#(on-click (du/logout) %))
|
||||
#(on-click (da/logout) %))
|
||||
|
||||
handle-logout-keydown
|
||||
(mf/use-fn
|
||||
(mf/deps on-click)
|
||||
(fn [event]
|
||||
(when (kbd/enter? event)
|
||||
(on-click (du/logout) event))))
|
||||
(on-click (da/logout) event))))
|
||||
|
||||
handle-set-profile
|
||||
(mf/use-fn
|
||||
(mf/deps on-click)
|
||||
(fn [event]
|
||||
(on-click :settings-profile event)))]
|
||||
#(on-click :settings-profile %))]
|
||||
|
||||
[:*
|
||||
(when (and team profile)
|
||||
|
@ -959,7 +959,9 @@
|
|||
:alt (:fullname profile)}]
|
||||
[:span {:class (stl/css :profile-fullname)} (:fullname profile)]]
|
||||
|
||||
[:& dropdown-menu {:on-close handle-close :show show :list-class (stl/css :profile-dropdown)}
|
||||
[:& dropdown-menu {:on-close on-close
|
||||
:show show
|
||||
:list-class (stl/css :profile-dropdown)}
|
||||
[:li {:tab-index (if show "0" "-1")
|
||||
:class (stl/css :profile-dropdown-item)
|
||||
:on-click handle-set-profile
|
||||
|
@ -1045,15 +1047,13 @@
|
|||
:show? show-comments?
|
||||
:on-show-comments handle-show-comments}])]]))
|
||||
|
||||
(mf/defc sidebar
|
||||
{::mf/wrap-props false
|
||||
(mf/defc sidebar*
|
||||
{::mf/props :obj
|
||||
::mf/wrap [mf/memo]}
|
||||
[props]
|
||||
(let [team (obj/get props "team")
|
||||
profile (obj/get props "profile")]
|
||||
[:nav {:class (stl/css :dashboard-sidebar) :data-testid "dashboard-sidebar"}
|
||||
[:> sidebar-content props]
|
||||
[:& profile-section
|
||||
{:profile profile
|
||||
:team team}]]))
|
||||
[{:keys [team profile] :as props}]
|
||||
[:nav {:class (stl/css :dashboard-sidebar) :data-testid "dashboard-sidebar"}
|
||||
[:> sidebar-content* props]
|
||||
[:> profile-section*
|
||||
{:profile profile
|
||||
:team team}]])
|
||||
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.schema :as sm]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
|
@ -31,6 +31,7 @@
|
|||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private arrow-icon
|
||||
|
@ -61,10 +62,10 @@
|
|||
{::mf/wrap [mf/memo]
|
||||
::mf/props :obj}
|
||||
[{:keys [section team]}]
|
||||
(let [on-nav-members (mf/use-fn #(st/emit! (dd/go-to-team-members)))
|
||||
on-nav-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings)))
|
||||
on-nav-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations)))
|
||||
on-nav-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks)))
|
||||
(let [on-nav-members (mf/use-fn #(st/emit! (dcm/go-to-dashboard-members)))
|
||||
on-nav-settings (mf/use-fn #(st/emit! (dcm/go-to-dashboard-settings)))
|
||||
on-nav-invitations (mf/use-fn #(st/emit! (dcm/go-to-dashboard-invitations)))
|
||||
on-nav-webhooks (mf/use-fn #(st/emit! (dcm/go-to-dashboard-webhooks)))
|
||||
|
||||
route (mf/deref refs/route)
|
||||
invite-email (-> route :query-params :invite-email)
|
||||
|
@ -139,12 +140,12 @@
|
|||
::mf/register-as :invite-members
|
||||
::mf/props :obj}
|
||||
[{:keys [team origin invite-email]}]
|
||||
(let [members-map (mf/deref refs/dashboard-team-members)
|
||||
perms (:permissions team)
|
||||
(let [members (get team :members)
|
||||
perms (get team :permissions)
|
||||
team-id (get team :id)
|
||||
|
||||
roles (mf/with-memo [perms]
|
||||
(get-available-roles perms))
|
||||
team-id (:id team)
|
||||
|
||||
initial (mf/with-memo [team-id invite-email]
|
||||
(if invite-email
|
||||
|
@ -156,7 +157,7 @@
|
|||
error-text (mf/use-state "")
|
||||
|
||||
current-data-emails (into #{} (dm/get-in @form [:clean-data :emails]))
|
||||
current-members-emails (into #{} (map (comp :email second)) members-map)
|
||||
current-members-emails (into #{} (map :email) members)
|
||||
|
||||
on-success
|
||||
(fn [_form {:keys [total]}]
|
||||
|
@ -164,8 +165,8 @@
|
|||
(st/emit! (ntf/success (tr "notifications.invitation-email-sent"))))
|
||||
|
||||
(st/emit! (modal/hide)
|
||||
(dd/fetch-team-members)
|
||||
(dd/fetch-team-invitations)))
|
||||
(dtm/fetch-members)
|
||||
(dtm/fetch-invitations)))
|
||||
|
||||
on-error
|
||||
(fn [_form cause]
|
||||
|
@ -198,11 +199,11 @@
|
|||
(let [params (:clean-data @form)
|
||||
mdata {:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)}]
|
||||
(st/emit! (-> (dd/invite-team-members (with-meta params mdata))
|
||||
(st/emit! (-> (dtm/create-invitations (with-meta params mdata))
|
||||
(with-meta {::ev/origin origin}))
|
||||
(dd/fetch-team-invitations)
|
||||
(dd/fetch-team-members (:id team)))))]
|
||||
|
||||
;; FIXME: looks duplicate
|
||||
(dtm/fetch-invitations)
|
||||
(dtm/fetch-members))))]
|
||||
|
||||
[:div {:class (stl/css-case :modal-team-container true
|
||||
:modal-team-container-workspace (= origin :workspace)
|
||||
|
@ -338,7 +339,8 @@
|
|||
(when is-you?
|
||||
[:li {:on-click on-leave
|
||||
:class (stl/css :action-dropdown-item)
|
||||
:key "is-you-option"} (tr "dashboard.leave-team")])
|
||||
:key "is-you-option"}
|
||||
(tr "dashboard.leave-team")])
|
||||
(when (and can-delete? (not is-you?) (not (and is-owner? (not owner?))))
|
||||
[:li {:on-click on-delete
|
||||
:class (stl/css :action-dropdown-item)
|
||||
|
@ -346,18 +348,18 @@
|
|||
|
||||
(defn- set-role! [member-id role]
|
||||
(let [params {:member-id member-id :role role}]
|
||||
(st/emit! (dd/update-team-member-role params))))
|
||||
(st/emit! (dtm/update-member-role params))))
|
||||
|
||||
(mf/defc team-member
|
||||
(mf/defc team-member*
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/props :obj}
|
||||
[{:keys [team member members profile]}]
|
||||
[{:keys [team member total-members profile]}]
|
||||
|
||||
(let [member-id (:id member)
|
||||
(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])
|
||||
owner? (dm/get-in team [:permissions :is-owner])
|
||||
|
||||
on-set-owner
|
||||
(mf/use-fn
|
||||
|
@ -373,18 +375,12 @@
|
|||
(st/emit! (modal/show params)))))
|
||||
|
||||
on-success
|
||||
(mf/use-fn
|
||||
(mf/deps profile)
|
||||
(fn []
|
||||
(st/emit! (dd/go-to-projects (:default-team-id profile))
|
||||
(modal/hide)
|
||||
(du/fetch-teams))))
|
||||
(mf/use-fn #(rx/of (dcm/go-to-dashboard-recent :team-id :default)))
|
||||
|
||||
on-error
|
||||
(mf/use-fn
|
||||
(fn [{:keys [code] :as error}]
|
||||
(condp = code
|
||||
|
||||
:no-enough-members-for-leave
|
||||
(rx/of (ntf/error (tr "errors.team-leave.insufficient-members")))
|
||||
|
||||
|
@ -400,17 +396,17 @@
|
|||
(mf/use-fn
|
||||
(mf/deps team on-success on-error)
|
||||
(fn []
|
||||
(st/emit! (dd/delete-team (with-meta team {:on-success on-success
|
||||
:on-error on-error})))))
|
||||
(st/emit! (dtm/delete-team (with-meta team {:on-success on-success
|
||||
:on-error on-error})))))
|
||||
|
||||
on-leave-accepted
|
||||
(mf/use-fn
|
||||
(mf/deps on-success on-error)
|
||||
(fn [member-id]
|
||||
(let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))]
|
||||
(st/emit! (dd/leave-team (with-meta params
|
||||
{:on-success on-success
|
||||
:on-error on-error}))))))
|
||||
(st/emit! (dtm/leave-current-team (with-meta params
|
||||
{:on-success on-success
|
||||
:on-error on-error}))))))
|
||||
|
||||
on-leave-and-close
|
||||
(mf/use-fn
|
||||
|
@ -428,7 +424,7 @@
|
|||
(mf/use-fn
|
||||
(mf/deps profile team on-leave-accepted)
|
||||
(fn []
|
||||
(st/emit! (dd/fetch-team-members (:id team))
|
||||
(st/emit! (dtm/fetch-members)
|
||||
(modal/show
|
||||
{:type :leave-and-reassign
|
||||
:profile profile
|
||||
|
@ -450,7 +446,7 @@
|
|||
(mf/use-fn
|
||||
(mf/deps member-id)
|
||||
(fn []
|
||||
(let [on-accept #(st/emit! (dd/delete-team-member {:member-id member-id}))
|
||||
(let [on-accept #(st/emit! (dtm/delete-member {:member-id member-id}))
|
||||
params {:type :confirm
|
||||
:title (tr "modals.delete-team-member-confirm.title")
|
||||
:message (tr "modals.delete-team-member-confirm.message")
|
||||
|
@ -459,7 +455,7 @@
|
|||
(st/emit! (modal/show params)))))
|
||||
|
||||
on-leave'
|
||||
(cond (= 1 (count members)) on-leave-and-close
|
||||
(cond (= 1 total-members) on-leave-and-close
|
||||
(= true owner?) on-change-owner-and-leave
|
||||
:else on-leave)]
|
||||
|
||||
|
@ -483,16 +479,26 @@
|
|||
:on-delete on-delete
|
||||
:on-leave on-leave'}]]]))
|
||||
|
||||
(mf/defc team-members
|
||||
{::mf/props :obj}
|
||||
[{:keys [members-map team profile]}]
|
||||
(let [members (mf/with-memo [members-map]
|
||||
(->> (vals members-map)
|
||||
(sort-by :created-at)
|
||||
(remove :is-owner)))
|
||||
owner (mf/with-memo [members-map]
|
||||
(->> (vals members-map)
|
||||
(d/seek :is-owner)))]
|
||||
(mf/defc team-members*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [team profile]}]
|
||||
(let [members (get team :members)
|
||||
|
||||
total-members
|
||||
(count members)
|
||||
|
||||
|
||||
owner
|
||||
(mf/with-memo [members]
|
||||
(d/seek :is-owner members))
|
||||
|
||||
members
|
||||
(mf/with-memo [team]
|
||||
(->> (:members team)
|
||||
(sort-by :created-at)
|
||||
(remove :is-owner)
|
||||
(vec)))]
|
||||
|
||||
[:div {:class (stl/css :dashboard-table :team-members)}
|
||||
[:div {:class (stl/css :table-header)}
|
||||
|
@ -500,42 +506,39 @@
|
|||
[:div {:class (stl/css :table-field :title-field-role)} (tr "labels.role")]]
|
||||
|
||||
[:div {:class (stl/css :table-rows)}
|
||||
[:& team-member
|
||||
[:> team-member*
|
||||
{:member owner
|
||||
:team team
|
||||
:profile profile
|
||||
:members members-map}]
|
||||
:total-members total-members}]
|
||||
|
||||
(for [item members]
|
||||
[:& team-member
|
||||
[:> team-member*
|
||||
{:member item
|
||||
:team team
|
||||
:profile profile
|
||||
:key (:id item)
|
||||
:members members-map}])]]))
|
||||
:key (dm/str (:id item))
|
||||
:total-members total-members}])]]))
|
||||
|
||||
(mf/defc team-members-page
|
||||
(mf/defc team-members-page*
|
||||
{::mf/props :obj}
|
||||
[{:keys [team profile]}]
|
||||
(let [members-map (mf/deref refs/dashboard-team-members)]
|
||||
(mf/with-effect [team]
|
||||
(dom/set-html-title
|
||||
(tr "title.team-members"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team)))))
|
||||
|
||||
(mf/with-effect [team]
|
||||
(dom/set-html-title
|
||||
(tr "title.team-members"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team)))))
|
||||
(mf/with-effect []
|
||||
(st/emit! (dtm/fetch-members)))
|
||||
|
||||
(mf/with-effect [team]
|
||||
(st/emit! (dd/fetch-team-members (:id team))))
|
||||
|
||||
[:*
|
||||
[:& header {:section :dashboard-team-members :team team}]
|
||||
[:section {:class (stl/css :dashboard-container :dashboard-team-members)}
|
||||
[:& team-members
|
||||
{:profile profile
|
||||
:team team
|
||||
:members-map members-map}]]]))
|
||||
[:*
|
||||
[:& header {:section :dashboard-team-members :team team}]
|
||||
[:section {:class (stl/css :dashboard-container :dashboard-team-members)}
|
||||
[:> team-members*
|
||||
{:profile profile
|
||||
:team team}]]])
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; INVITATIONS SECTION
|
||||
|
@ -587,8 +590,9 @@
|
|||
:on-click on-change'}
|
||||
(tr "labels.viewer")]]]]))
|
||||
|
||||
(mf/defc invitation-actions
|
||||
{::mf/props :obj}
|
||||
(mf/defc invitation-actions*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [invitation team-id]}]
|
||||
(let [show? (mf/use-state false)
|
||||
|
||||
|
@ -622,15 +626,15 @@
|
|||
(mf/deps email team-id)
|
||||
(fn []
|
||||
(let [params {:email email :team-id team-id}
|
||||
mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}]
|
||||
(st/emit! (dd/delete-team-invitation (with-meta params mdata))))))
|
||||
mdata {:on-success #(st/emit! (dtm/fetch-invitations))}]
|
||||
(st/emit! (dtm/delete-invitation (with-meta params mdata))))))
|
||||
|
||||
on-resend-success
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(st/emit! (ntf/success (tr "notifications.invitation-email-sent"))
|
||||
(modal/hide)
|
||||
(dd/fetch-team-invitations))))
|
||||
(dtm/fetch-invitations))))
|
||||
|
||||
on-resend
|
||||
(mf/use-fn
|
||||
|
@ -643,7 +647,7 @@
|
|||
{:on-success on-resend-success
|
||||
:on-error on-error})]
|
||||
(st/emit!
|
||||
(-> (dd/invite-team-members params)
|
||||
(-> (dtm/create-invitations params)
|
||||
(with-meta {::ev/origin :team}))))))
|
||||
|
||||
on-copy-success
|
||||
|
@ -660,7 +664,7 @@
|
|||
{:on-success on-copy-success
|
||||
:on-error on-error})]
|
||||
(st/emit!
|
||||
(-> (dd/copy-invitation-link params)
|
||||
(-> (dtm/copy-invitation-link params)
|
||||
(with-meta {::ev/origin :team}))))))
|
||||
|
||||
on-hide (mf/use-fn #(reset! show? false))
|
||||
|
@ -694,17 +698,19 @@
|
|||
role (:role invitation)
|
||||
status (if expired? :expired :pending)
|
||||
type (if expired? :warning :default)
|
||||
badge-content (if (= status :expired)
|
||||
(tr "labels.expired-invitation")
|
||||
(tr "labels.pending-invitation"))
|
||||
|
||||
badge-content
|
||||
(if (= status :expired)
|
||||
(tr "labels.expired-invitation")
|
||||
(tr "labels.pending-invitation"))
|
||||
|
||||
on-change-role
|
||||
(mf/use-fn
|
||||
(mf/deps email team-id)
|
||||
(fn [role _event]
|
||||
(let [params {:email email :team-id team-id :role role}
|
||||
mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}]
|
||||
(st/emit! (dd/update-team-invitation-role (with-meta params mdata))))))]
|
||||
mdata {:on-success #(st/emit! (dtm/fetch-invitations))}]
|
||||
(st/emit! (dtm/update-invitation-role (with-meta params mdata))))))]
|
||||
|
||||
[:div {:class (stl/css :table-row :table-row-invitations)}
|
||||
[:div {:class (stl/css :table-field :field-email)} email]
|
||||
|
@ -720,25 +726,36 @@
|
|||
[:& badge-notification {:type type :content badge-content}]]
|
||||
|
||||
[:div {:class (stl/css :table-field :field-actions)}
|
||||
(when can-invite
|
||||
[:& invitation-actions
|
||||
(when ^boolean can-invite
|
||||
[:> invitation-actions*
|
||||
{:invitation invitation
|
||||
:team-id team-id}])]]))
|
||||
|
||||
(mf/defc empty-invitation-table
|
||||
[{:keys [can-invite] :as props}]
|
||||
(mf/defc empty-invitation-table*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [can-invite]}]
|
||||
[:div {:class (stl/css :empty-invitations)}
|
||||
[:span (tr "labels.no-invitations")]
|
||||
(when can-invite
|
||||
(when ^boolean can-invite
|
||||
[:> i18n/tr-html* {:content (tr "labels.no-invitations-hint")
|
||||
:tag-name "span"}])])
|
||||
|
||||
(mf/defc invitation-section
|
||||
[{:keys [team invitations] :as props}]
|
||||
(let [owner? (dm/get-in team [:permissions :is-owner])
|
||||
admin? (dm/get-in team [:permissions :is-admin])
|
||||
can-invite (or owner? admin?)
|
||||
team-id (:id team)]
|
||||
(def ^:private ref:invitations
|
||||
(l/derived :invitations st/state))
|
||||
|
||||
(mf/defc invitation-section*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [team]}]
|
||||
(let [permissions (get team :permissions)
|
||||
invitations (mf/deref ref:invitations)
|
||||
|
||||
team-id (get team :id)
|
||||
|
||||
owner? (get permissions :is-owner)
|
||||
admin? (get permissions :is-admin)
|
||||
can-invite? (or owner? admin?)]
|
||||
|
||||
[:div {:class (stl/css :invitations)}
|
||||
[:div {:class (stl/css :table-header)}
|
||||
|
@ -746,38 +763,34 @@
|
|||
[:div {:class (stl/css :title-field-role)} (tr "labels.role")]
|
||||
[:div {:class (stl/css :title-field-status)} (tr "labels.status")]]
|
||||
(if (empty? invitations)
|
||||
[:& empty-invitation-table {:can-invite can-invite}]
|
||||
[:> empty-invitation-table* {:can-invite can-invite?}]
|
||||
[:div {:class (stl/css :table-rows)}
|
||||
(for [invitation invitations]
|
||||
[:> invitation-row*
|
||||
{:key (:email invitation)
|
||||
:invitation invitation
|
||||
:can-invite can-invite
|
||||
:can-invite can-invite?
|
||||
:team-id team-id}])])]))
|
||||
|
||||
(mf/defc team-invitations-page
|
||||
(mf/defc team-invitations-page*
|
||||
{::mf/props :obj}
|
||||
[{:keys [team]}]
|
||||
(let [invitations (mf/deref refs/dashboard-team-invitations)]
|
||||
|
||||
(mf/with-effect [team]
|
||||
(dom/set-html-title
|
||||
(tr "title.team-invitations"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team)))))
|
||||
(mf/with-effect [team]
|
||||
(dom/set-html-title
|
||||
(tr "title.team-invitations"
|
||||
(if (:is-default team)
|
||||
(tr "dashboard.your-penpot")
|
||||
(:name team)))))
|
||||
|
||||
(mf/with-effect []
|
||||
(st/emit! (dd/fetch-team-invitations)))
|
||||
(mf/with-effect []
|
||||
(st/emit! (dtm/fetch-invitations)))
|
||||
|
||||
[:*
|
||||
[:& header {:section :dashboard-team-invitations
|
||||
:team team}]
|
||||
[:section {:class (stl/css :dashboard-team-invitations)}
|
||||
;; TODO: We should consider adding a "loading state" here
|
||||
;; with an (if (nil? invitations) [:& loading-state] [:& invitations])
|
||||
(when-not (nil? invitations)
|
||||
[:& invitation-section {:team team
|
||||
:invitations invitations}])]]))
|
||||
[:*
|
||||
[:& header {:section :dashboard-team-invitations
|
||||
:team team}]
|
||||
[:section {:class (stl/css :dashboard-team-invitations)}
|
||||
[:> invitation-section* {:team team}]]])
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; WEBHOOKS SECTION
|
||||
|
@ -811,9 +824,8 @@
|
|||
(mf/use-fn
|
||||
(fn [_]
|
||||
(let [message (tr "dashboard.webhooks.create.success")]
|
||||
(st/emit! (dd/fetch-team-webhooks)
|
||||
(ntf/success message)
|
||||
(modal/hide)))))
|
||||
(rx/of (ntf/success message)
|
||||
(modal/hide)))))
|
||||
|
||||
on-error
|
||||
(mf/use-fn
|
||||
|
@ -846,7 +858,7 @@
|
|||
params {:uri (:uri cdata)
|
||||
:mtype (:mtype cdata)
|
||||
:is-active (:is-active cdata)}]
|
||||
(st/emit! (dd/create-team-webhook
|
||||
(st/emit! (dtm/create-webhook
|
||||
(with-meta params mdata))))))
|
||||
|
||||
on-update-submit
|
||||
|
@ -855,7 +867,7 @@
|
|||
(let [params (:clean-data @form)
|
||||
mdata {:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)}]
|
||||
(st/emit! (dd/update-team-webhook
|
||||
(st/emit! (dtm/update-webhook
|
||||
(with-meta params mdata))))))
|
||||
|
||||
on-submit
|
||||
|
@ -910,7 +922,7 @@
|
|||
(tr "modals.edit-webhook.submit-label")
|
||||
(tr "modals.create-webhook.submit-label"))}]]]]]]))
|
||||
|
||||
(mf/defc webhooks-hero
|
||||
(mf/defc webhooks-hero*
|
||||
{::mf/props :obj}
|
||||
[]
|
||||
[:div {:class (stl/css :webhooks-hero-container)}
|
||||
|
@ -922,7 +934,7 @@
|
|||
:on-click #(st/emit! (modal/show :webhook {}))}
|
||||
(tr "dashboard.webhooks.create")]])
|
||||
|
||||
(mf/defc webhook-actions
|
||||
(mf/defc webhook-actions*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [on-edit on-delete can-edit]}]
|
||||
|
@ -945,8 +957,10 @@
|
|||
:class (stl/css :menu-disabled)}
|
||||
[:> icon* {:id "menu"}]])))
|
||||
|
||||
(mf/defc webhook-item
|
||||
{::mf/wrap [mf/memo]}
|
||||
(mf/defc webhook-item*
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [webhook permissions]}]
|
||||
(let [error-code (:error-code webhook)
|
||||
id (:id webhook)
|
||||
|
@ -966,8 +980,8 @@
|
|||
(mf/deps id)
|
||||
(fn []
|
||||
(let [params {:id id}
|
||||
mdata {:on-success #(st/emit! (dd/fetch-team-webhooks))}]
|
||||
(st/emit! (dd/delete-team-webhook (with-meta params mdata))))))
|
||||
mdata {:on-success #(st/emit! (dtm/fetch-webhooks))}]
|
||||
(st/emit! (dtm/delete-webhook (with-meta params mdata))))))
|
||||
|
||||
on-delete
|
||||
(mf/use-fn
|
||||
|
@ -1005,22 +1019,29 @@
|
|||
(tr "labels.active")
|
||||
(tr "labels.inactive"))]]
|
||||
[:div {:class (stl/css :table-field :actions)}
|
||||
[:& webhook-actions
|
||||
[:> webhook-actions*
|
||||
{:on-edit on-edit
|
||||
:on-delete on-delete
|
||||
:can-edit can-edit}]]]))
|
||||
|
||||
(mf/defc webhooks-list
|
||||
{::mf/props :obj}
|
||||
(mf/defc webhooks-list*
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [webhooks permissions]}]
|
||||
[:div {:class (stl/css :table-rows :webhook-table)}
|
||||
(for [webhook webhooks]
|
||||
[:& webhook-item {:webhook webhook :key (:id webhook) :permissions permissions}])])
|
||||
[:> webhook-item*
|
||||
{:webhook webhook
|
||||
:key (dm/str (:id webhook))
|
||||
:permissions permissions}])])
|
||||
|
||||
(mf/defc team-webhooks-page
|
||||
(def ^:private ref:webhooks
|
||||
(l/derived :webhooks st/state))
|
||||
|
||||
(mf/defc webhooks-page*
|
||||
{::mf/props :obj}
|
||||
[{:keys [team]}]
|
||||
(let [webhooks (mf/deref refs/dashboard-team-webhooks)]
|
||||
(let [webhooks (mf/deref ref:webhooks)]
|
||||
|
||||
(mf/with-effect [team]
|
||||
(dom/set-html-title
|
||||
|
@ -1030,33 +1051,34 @@
|
|||
(:name team)))))
|
||||
|
||||
(mf/with-effect [team]
|
||||
(st/emit! (dd/fetch-team-webhooks)))
|
||||
(st/emit! (dtm/fetch-webhooks)))
|
||||
|
||||
[:*
|
||||
[:& header {:team team :section :dashboard-team-webhooks}]
|
||||
[:section {:class (stl/css :dashboard-container :dashboard-team-webhooks)}
|
||||
[:*
|
||||
[:& webhooks-hero]
|
||||
[:> webhooks-hero* {}]
|
||||
(if (empty? webhooks)
|
||||
[:div {:class (stl/css :webhooks-empty)}
|
||||
[:div (tr "dashboard.webhooks.empty.no-webhooks")]
|
||||
[:div (tr "dashboard.webhooks.empty.add-one")]]
|
||||
[:& webhooks-list {:webhooks webhooks :permissions (:permissions team)}])]]]))
|
||||
[:> webhooks-list*
|
||||
{:webhooks webhooks
|
||||
:permissions (:permissions team)}])]]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SETTINGS SECTION
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(mf/defc team-settings-page
|
||||
(mf/defc team-settings-page*
|
||||
{::mf/props :obj}
|
||||
[{:keys [team]}]
|
||||
(let [finput (mf/use-ref)
|
||||
|
||||
members-map (mf/deref refs/dashboard-team-members)
|
||||
owner (->> (vals members-map)
|
||||
(d/seek :is-owner))
|
||||
members (get team :members)
|
||||
stats (get team :stats)
|
||||
|
||||
stats (mf/deref refs/dashboard-team-stats)
|
||||
owner (d/seek :is-owner members)
|
||||
|
||||
permissions (:permissions team)
|
||||
can-edit (or (:is-owner permissions)
|
||||
|
@ -1067,8 +1089,7 @@
|
|||
|
||||
on-file-selected
|
||||
(fn [file]
|
||||
(st/emit! (dd/update-team-photo file)))]
|
||||
|
||||
(st/emit! (dtm/update-team-photo file)))]
|
||||
|
||||
(mf/with-effect [team]
|
||||
(dom/set-html-title (tr "title.team-settings"
|
||||
|
@ -1076,11 +1097,9 @@
|
|||
(tr "dashboard.your-penpot")
|
||||
(:name team)))))
|
||||
|
||||
|
||||
(mf/with-effect [team]
|
||||
(let [team-id (:id team)]
|
||||
(st/emit! (dd/fetch-team-members team-id)
|
||||
(dd/fetch-team-stats team-id))))
|
||||
(mf/with-effect []
|
||||
(st/emit! (dtm/fetch-members)
|
||||
(dtm/fetch-stats)))
|
||||
|
||||
[:*
|
||||
[:& header {:section :dashboard-team-settings :team team}]
|
||||
|
@ -1116,7 +1135,7 @@
|
|||
[:div {:class (stl/css :block-content)}
|
||||
user-icon
|
||||
[:span {:class (stl/css :block-text)}
|
||||
(tr "dashboard.num-of-members" (count members-map))]]]
|
||||
(tr "dashboard.num-of-members" (count members))]]]
|
||||
|
||||
[:div {:class (stl/css :block)}
|
||||
[:div {:class (stl/css :block-label)}
|
||||
|
|
|
@ -8,17 +8,17 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -28,15 +28,15 @@
|
|||
|
||||
(defn- on-create-success
|
||||
[_form response]
|
||||
(let [msg "Team created successfully"]
|
||||
(st/emit! (ntf/success msg)
|
||||
(modal/hide)
|
||||
(rt/nav :dashboard-projects {:team-id (:id response)}))))
|
||||
(let [message "Team created successfully"
|
||||
team-id (:id response)]
|
||||
(st/emit! (ntf/success message)
|
||||
(dcm/go-to-dashboard-recent :team-id team-id))))
|
||||
|
||||
(defn- on-update-success
|
||||
[_form _response]
|
||||
(let [msg "Team created successfully"]
|
||||
(st/emit! (ntf/success msg)
|
||||
(let [message "Team created successfully"]
|
||||
(st/emit! (ntf/success message)
|
||||
(modal/hide))))
|
||||
|
||||
(defn- on-error
|
||||
|
@ -51,7 +51,7 @@
|
|||
(let [mdata {:on-success (partial on-create-success form)
|
||||
:on-error (partial on-error form)}
|
||||
params {:name (get-in @form [:clean-data :name])}]
|
||||
(st/emit! (-> (dd/create-team (with-meta params mdata))
|
||||
(st/emit! (-> (dtm/create-team (with-meta params mdata))
|
||||
(with-meta {::ev/origin :dashboard})))))
|
||||
|
||||
(defn- on-update-submit
|
||||
|
@ -59,7 +59,7 @@
|
|||
(let [mdata {:on-success (partial on-update-success form)
|
||||
:on-error (partial on-error form)}
|
||||
team (get @form :clean-data)]
|
||||
(st/emit! (dd/update-team (with-meta team mdata))
|
||||
(st/emit! (dtm/update-team (with-meta team mdata))
|
||||
(modal/hide))))
|
||||
|
||||
(defn- on-submit
|
||||
|
|
|
@ -9,8 +9,9 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.config :as cf]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
|
@ -18,7 +19,6 @@
|
|||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :as storage]
|
||||
[okulary.core :as l]
|
||||
[potok.v2.core :as ptk]
|
||||
|
@ -43,9 +43,9 @@
|
|||
:section section})
|
||||
|
||||
(when-not (some? project-id)
|
||||
(rt/nav :dashboard-files
|
||||
{:team-id team-id
|
||||
:project-id default-project-id}))))]
|
||||
(dcm/go-to-dashboard-recent
|
||||
:team-id team-id
|
||||
:project-id default-project-id))))]
|
||||
|
||||
(st/emit!
|
||||
(ptk/event ::ev/event {::ev/name "import-template-launch"
|
||||
|
@ -157,8 +157,8 @@
|
|||
[:div {:class (stl/css :template-link-title)} (tr "dashboard.libraries-and-templates")]
|
||||
[:div {:class (stl/css :template-link-text)} (tr "dashboard.libraries-and-templates.explore")]]]]]]))
|
||||
|
||||
(mf/defc templates-section
|
||||
{::mf/wrap-props false}
|
||||
(mf/defc templates-section*
|
||||
{::mf/props :obj}
|
||||
[{:keys [default-project-id profile project-id team-id]}]
|
||||
(let [templates (mf/deref builtin-templates)
|
||||
templates (mf/with-memo [templates]
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]]
|
||||
[app.util.dom :as dom]
|
||||
[rumext.v2 :as mf]))
|
||||
|
@ -25,17 +26,25 @@
|
|||
{::mf/props :obj
|
||||
::mf/forward-ref true
|
||||
::mf/schema schema:input}
|
||||
[{:keys [icon class type external-ref] :rest props}]
|
||||
(let [ref (or external-ref (mf/use-ref))
|
||||
type (or type "text")
|
||||
icon-class (stl/css-case :input true
|
||||
:input-with-icon (some? icon))
|
||||
props (mf/spread-props props {:class icon-class :ref ref :type type})
|
||||
handle-icon-click (mf/use-fn (mf/deps ref)
|
||||
(fn [_]
|
||||
(let [input-node (mf/ref-val ref)]
|
||||
(dom/select-node input-node)
|
||||
(dom/focus! input-node))))]
|
||||
[:> "span" {:class (dm/str class " " (stl/css :container))}
|
||||
(when icon [:> icon* {:id icon :class (stl/css :icon) :on-click handle-icon-click}])
|
||||
[:> "input" props]]))
|
||||
[{:keys [icon class type] :rest props} ref]
|
||||
(let [ref (or ref (mf/use-ref))
|
||||
type (d/nilv type "text")
|
||||
props (mf/spread-props props
|
||||
:class (stl/css-case
|
||||
:input true
|
||||
:input-with-icon (some? icon))
|
||||
:ref ref
|
||||
:type type)
|
||||
|
||||
on-icon-click
|
||||
(mf/use-fn
|
||||
(mf/deps ref)
|
||||
(fn [_event]
|
||||
(let [input-node (mf/ref-val ref)]
|
||||
(dom/select-node input-node)
|
||||
(dom/focus! input-node))))]
|
||||
|
||||
[:> :span {:class (dm/str class " " (stl/css :container))}
|
||||
(when (some? icon)
|
||||
[:> icon* {:id icon :class (stl/css :icon) :on-click on-icon-click}])
|
||||
[:> :input props]]))
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
(ns app.main.ui.onboarding.newsletter
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.data.events :as-alias ev]
|
||||
[app.main.data.event :as-alias ev]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.events :as-alias ev]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.event :as-alias ev]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.icons :as i]
|
||||
|
|
|
@ -9,15 +9,15 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.notifications.context-notification :refer [context-notification]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -84,7 +84,7 @@
|
|||
(st/emit! (du/update-profile-props {:onboarding-team-id team-id
|
||||
:onboarding-viewed true})
|
||||
(when go-to-team?
|
||||
(rt/nav :dashboard-projects {:team-id team-id}))))))
|
||||
(dcm/go-to-dashboard-recent :team-id team-id))))))
|
||||
|
||||
on-error
|
||||
(mf/use-fn
|
||||
|
@ -117,7 +117,7 @@
|
|||
(let [mdata {:on-success on-success
|
||||
:on-error on-error}
|
||||
params {:name name}]
|
||||
(st/emit! (-> (dd/create-team (with-meta params mdata))
|
||||
(st/emit! (-> (dtm/create-team (with-meta params mdata))
|
||||
(with-meta {::ev/origin :onboarding-without-invitations}))
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "onboarding-step"
|
||||
|
@ -133,7 +133,7 @@
|
|||
(let [mdata {:on-success on-success
|
||||
:on-error on-error}]
|
||||
|
||||
(st/emit! (-> (dd/create-team-with-invitations (with-meta params mdata))
|
||||
(st/emit! (-> (dtm/create-team-with-invitations (with-meta params mdata))
|
||||
(with-meta {::ev/origin :onboarding-with-invitations}))
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "onboarding-step"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
(ns app.main.ui.releases
|
||||
(:require
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.releases.common :as rc]
|
||||
[app.main.ui.releases.v1-10]
|
||||
|
|
|
@ -7,34 +7,17 @@
|
|||
(ns app.main.ui.routes
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uri :as u]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.util.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::file-id ::us/uuid)
|
||||
(s/def ::section ::us/keyword)
|
||||
(s/def ::index ::us/integer)
|
||||
(s/def ::token (s/nilable ::us/not-empty-string))
|
||||
(s/def ::share-id ::us/uuid)
|
||||
|
||||
(s/def ::viewer-path-params
|
||||
(s/keys :req-un [::file-id]))
|
||||
|
||||
(s/def ::viewer-query-params
|
||||
(s/keys :opt-un [::index ::share-id ::section ::page-id]))
|
||||
|
||||
(s/def ::any any?)
|
||||
|
||||
(def routes
|
||||
[["/auth"
|
||||
["/login" :auth-login]
|
||||
|
@ -53,11 +36,10 @@
|
|||
["/access-tokens" :settings-access-tokens]]
|
||||
|
||||
["/frame-preview" :frame-preview]
|
||||
["/view/:file-id"
|
||||
{:name :viewer
|
||||
:conform
|
||||
{:path-params ::viewer-path-params
|
||||
:query-params ::viewer-query-params}}]
|
||||
|
||||
["/view" :viewer]
|
||||
|
||||
["/view/:file-id" :viewer-legacy]
|
||||
|
||||
(when *assert*
|
||||
["/debug/icons-preview" :debug-icons-preview])
|
||||
|
@ -65,33 +47,32 @@
|
|||
;; Used for export
|
||||
["/render-sprite/:file-id" :render-sprite]
|
||||
|
||||
["/dashboard/team/:team-id"
|
||||
["/members" :dashboard-team-members]
|
||||
["/invitations" :dashboard-team-invitations]
|
||||
["/webhooks" :dashboard-team-webhooks]
|
||||
["/settings" :dashboard-team-settings]
|
||||
["/projects" :dashboard-projects]
|
||||
["/dashboard"
|
||||
["/members" :dashboard-members]
|
||||
["/invitations" :dashboard-invitations]
|
||||
["/webhooks" :dashboard-webhooks]
|
||||
["/settings" :dashboard-settings]
|
||||
["/recent" :dashboard-recent]
|
||||
["/search" :dashboard-search]
|
||||
["/fonts" :dashboard-fonts]
|
||||
["/fonts/providers" :dashboard-font-providers]
|
||||
["/libraries" :dashboard-libraries]
|
||||
["/projects/:project-id" :dashboard-files]]
|
||||
["/files" :dashboard-files]]
|
||||
|
||||
["/workspace/:project-id/:file-id" :workspace]])
|
||||
["/dashboard/team/:team-id"
|
||||
["/members" :dashboard-legacy-team-members]
|
||||
["/invitations" :dashboard-legacy-team-invitations]
|
||||
["/webhooks" :dashboard-legacy-team-webhooks]
|
||||
["/settings" :dashboard-legacy-team-settings]
|
||||
["/projects" :dashboard-legacy-projects]
|
||||
["/search" :dashboard-legacy-search]
|
||||
["/fonts" :dashboard-legacy-fonts]
|
||||
["/fonts/providers" :dashboard-legacy-font-providers]
|
||||
["/libraries" :dashboard-legacy-libraries]
|
||||
["/projects/:project-id" :dashboard-legacy-files]]
|
||||
|
||||
(defn- match-path
|
||||
[router path]
|
||||
(when-let [match (rt/match router path)]
|
||||
(if-let [conform (get-in match [:data :conform])]
|
||||
(let [spath (get conform :path-params ::any)
|
||||
squery (get conform :query-params ::any)]
|
||||
(try
|
||||
(-> (dissoc match :params)
|
||||
(assoc :path-params (us/conform spath (get match :path-params))
|
||||
:query-params (us/conform squery (get match :query-params))))
|
||||
(catch :default _
|
||||
nil)))
|
||||
match)))
|
||||
["/workspace" :workspace]
|
||||
["/workspace/:project-id/:file-id" :workspace-legacy]])
|
||||
|
||||
(defn on-navigate
|
||||
[router path]
|
||||
|
@ -99,8 +80,9 @@
|
|||
[base-path qs] (str/split path "?")
|
||||
location-path (dm/str (.-origin location) (.-pathname location))
|
||||
valid-location? (= location-path (dm/str cf/public-uri))
|
||||
match (match-path router path)
|
||||
match (rt/match router path)
|
||||
empty-path? (or (= base-path "") (= base-path "/"))]
|
||||
|
||||
(cond
|
||||
(not valid-location?)
|
||||
(st/emit! (rt/assign-exception {:type :not-found}))
|
||||
|
@ -119,7 +101,11 @@
|
|||
(st/emit! (rt/nav :auth-login))
|
||||
|
||||
empty-path?
|
||||
(st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)} (u/query-string->map qs)))
|
||||
(let [team-id (or (dtm/get-last-team-id)
|
||||
(:default-team-id profile))]
|
||||
(st/emit! (rt/nav :dashboard-recent
|
||||
(-> (u/query-string->map qs)
|
||||
(assoc :team-id team-id)))))
|
||||
|
||||
:else
|
||||
(st/emit! (rt/assign-exception {:type :not-found})))))))))
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
(:require
|
||||
[app.main.data.dashboard.shortcuts :as sc]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.settings.access-tokens :refer [access-tokens-page]]
|
||||
|
@ -20,7 +21,6 @@
|
|||
[app.main.ui.settings.profile :refer [profile-page]]
|
||||
[app.main.ui.settings.sidebar :refer [sidebar]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc header
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
[app.common.schema :as sm]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
[app.common.schema :as sm]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(:require
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.notifications.context-notification :refer [context-notification]]
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
|
@ -24,7 +24,7 @@
|
|||
(defn- on-success
|
||||
[profile]
|
||||
(st/emit! (ntf/success (tr "notifications.profile-saved"))
|
||||
(du/profile-fetched profile)))
|
||||
(du/initialize-profile profile)))
|
||||
|
||||
(defn- on-submit
|
||||
[form _event]
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as udu]
|
||||
[app.main.data.profile :as udu]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.util.dom :as dom]
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
[app.config :as cf]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.profile :as du]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||
|
|
|
@ -8,15 +8,16 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.config :as cf]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.dashboard.sidebar :refer [profile-section]]
|
||||
[app.main.ui.dashboard.sidebar :refer [profile-section*]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.router :as rt]
|
||||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -26,6 +27,7 @@
|
|||
(def ^:private feedback-icon
|
||||
(i/icon-xref :feedback (stl/css :feedback-icon)))
|
||||
|
||||
;; FIXME: move to common
|
||||
(def ^:private go-settings-profile
|
||||
#(st/emit! (rt/nav :settings-profile)))
|
||||
|
||||
|
@ -58,12 +60,13 @@
|
|||
options? (= section :settings-options)
|
||||
feedback? (= section :settings-feedback)
|
||||
access-tokens? (= section :settings-access-tokens)
|
||||
team-id (du/get-current-team-id profile)
|
||||
team-id (or (dtm/get-last-team-id)
|
||||
(:default-team-id profile))
|
||||
|
||||
go-dashboard
|
||||
(mf/use-fn
|
||||
(mf/deps team-id)
|
||||
#(st/emit! (rt/nav :dashboard-projects {:team-id team-id})))]
|
||||
#(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))]
|
||||
|
||||
[:div {:class (stl/css :sidebar-content)}
|
||||
[:div {:class (stl/css :sidebar-content-section)}
|
||||
|
@ -119,5 +122,5 @@
|
|||
[:div {:class (stl/css :dashboard-sidebar :settings)}
|
||||
[:& sidebar-content {:profile profile
|
||||
:section section}]
|
||||
[:& profile-section {:profile profile}]])
|
||||
[:> profile-section* {:profile profile}]])
|
||||
|
||||
|
|
|
@ -11,22 +11,22 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.uri :as u]
|
||||
[app.main.data.common :as dc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.auth.login :refer [login-methods]]
|
||||
[app.main.ui.auth.recovery-request :refer [recovery-request-page recovery-sent-page]]
|
||||
[app.main.ui.auth.register :as register]
|
||||
[app.main.ui.dashboard.sidebar :refer [sidebar]]
|
||||
[app.main.ui.dashboard.sidebar :refer [sidebar*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*]]
|
||||
[app.main.ui.ds.foundations.assets.raw-svg :refer [raw-svg*]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.viewer.header :as viewer.header]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -213,7 +213,8 @@
|
|||
(mf/use-fn
|
||||
(mf/deps profile)
|
||||
(fn []
|
||||
(st/emit! (rt/nav :dashboard-projects {:team-id (:default-team-id profile)}))))
|
||||
(let [team-id (:default-team-id profile)]
|
||||
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id)))))
|
||||
|
||||
on-success
|
||||
(mf/use-fn
|
||||
|
@ -233,7 +234,7 @@
|
|||
{:team-id team-id})
|
||||
mdata {:on-success on-success
|
||||
:on-error on-error}]
|
||||
(st/emit! (dc/create-team-access-request
|
||||
(st/emit! (dcm/create-team-access-request
|
||||
(with-meta params mdata))))))]
|
||||
|
||||
[:*
|
||||
|
@ -267,7 +268,7 @@
|
|||
|
||||
[:div {:class (stl/css :dashboard)}
|
||||
[:div {:class (stl/css :dashboard-sidebar)}
|
||||
[:& sidebar
|
||||
[:> sidebar*
|
||||
{:team nil
|
||||
:projects []
|
||||
:project (:default-project-id profile)
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.viewer.comments :refer [comments-layer comments-sidebar]]
|
||||
[app.main.ui.viewer.comments :refer [comments-layer comments-sidebar*]]
|
||||
[app.main.ui.viewer.header :as header]
|
||||
[app.main.ui.viewer.inspect :as inspect]
|
||||
[app.main.ui.viewer.interactions :as interactions]
|
||||
|
@ -129,8 +129,8 @@
|
|||
:comment-sidebar show-sidebar?}]
|
||||
|
||||
(when show-sidebar?
|
||||
[:& comments-sidebar
|
||||
{:users users
|
||||
[:> comments-sidebar*
|
||||
{:profiles users
|
||||
:frame frame
|
||||
:page page}])]))
|
||||
|
||||
|
@ -274,9 +274,9 @@
|
|||
:page page
|
||||
:zoom zoom}])]])
|
||||
|
||||
(mf/defc viewer-content
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [data page-id share-id section index interactions-mode share] :as props}]
|
||||
(mf/defc viewer-content*
|
||||
{::mf/props :obj}
|
||||
[{:keys [data page-id share-id section index interactions-mode share]}]
|
||||
(let [{:keys [file users project permissions]} data
|
||||
allowed (or
|
||||
(= section :interactions)
|
||||
|
@ -620,8 +620,8 @@
|
|||
|
||||
;; --- Component: Viewer
|
||||
|
||||
(mf/defc viewer
|
||||
{::mf/wrap-props false}
|
||||
(mf/defc viewer*
|
||||
{::mf/props :obj}
|
||||
[{:keys [file-id share-id page-id] :as props}]
|
||||
(mf/with-effect [file-id page-id share-id]
|
||||
(let [params {:file-id file-id
|
||||
|
@ -630,9 +630,10 @@
|
|||
(st/emit! (dv/initialize params))
|
||||
(fn []
|
||||
(st/emit! (dv/finalize params)))))
|
||||
|
||||
(if-let [data (mf/deref refs/viewer-data)]
|
||||
(let [props (obj/merge props #js {:data data :key (dm/str file-id)})]
|
||||
[:> viewer-content props])
|
||||
[:> viewer-content* props])
|
||||
|
||||
[:> loader* {:title (tr "labels.loading")
|
||||
:overlay true}]))
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
[app.common.geom.rect :as grc]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.comments :as cmt]
|
||||
|
@ -220,7 +220,7 @@
|
|||
{:thread thread
|
||||
:position-modifier modifier1
|
||||
:viewport {:offset-x 0 :offset-y 0 :width (:width vsize) :height (:height vsize)}
|
||||
:users users
|
||||
:profiles users
|
||||
:zoom zoom}])
|
||||
|
||||
(when-let [draft (:draft local)]
|
||||
|
@ -231,10 +231,11 @@
|
|||
:on-submit on-draft-submit
|
||||
:zoom zoom}])]]]))
|
||||
|
||||
(mf/defc comments-sidebar
|
||||
[{:keys [users frame page]}]
|
||||
(mf/defc comments-sidebar*
|
||||
{::mf/props :obj}
|
||||
[{:keys [profiles frame page]}]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
local (mf/deref refs/comments-local)
|
||||
local (mf/deref refs/comments-local)
|
||||
threads-map (mf/deref refs/comment-threads)
|
||||
threads (->> (vals threads-map)
|
||||
(dcm/apply-filters local profile)
|
||||
|
@ -242,4 +243,8 @@
|
|||
(gsh/has-point? frame position))))]
|
||||
[:aside {:class (stl/css :comments-sidebar)}
|
||||
[:div {:class (stl/css :settings-bar-inside)}
|
||||
[:& wc/comments-sidebar {:from-viewer true :users users :threads threads :page-id (:id page)}]]]))
|
||||
[:> wc/comments-sidebar*
|
||||
{:from-viewer true
|
||||
:profiles profiles
|
||||
:threads threads
|
||||
:page-id (:id page)}]]]))
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
[app.main.ui.formats :as fmt]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.viewer.comments :refer [comments-menu]]
|
||||
[app.main.ui.viewer.interactions :refer [flows-menu* interactions-menu]]
|
||||
[app.main.ui.viewer.interactions :refer [flows-menu* interactions-menu*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[okulary.core :as l]
|
||||
|
@ -173,7 +173,8 @@
|
|||
:interactions [:*
|
||||
(when index
|
||||
[:> flows-menu* {:page page :index index}])
|
||||
[:& interactions-menu {:interactions-mode interactions-mode}]]
|
||||
[:> interactions-menu*
|
||||
{:interactions-mode interactions-mode}]]
|
||||
:comments [:& comments-menu]
|
||||
[:div {:class (stl/css :view-options)}])
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.config :as cfg]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
|
@ -30,6 +30,7 @@
|
|||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -49,13 +50,26 @@
|
|||
</body>
|
||||
</html>")
|
||||
|
||||
;; FIXME: this code need to be refactored
|
||||
(defn get-viewer-objects
|
||||
([]
|
||||
(let [route (deref refs/route)
|
||||
page-id (:page-id (:query-params route))]
|
||||
(get-viewer-objects page-id)))
|
||||
([page-id]
|
||||
(l/derived
|
||||
(fn [state]
|
||||
(let [objects (refs/get-viewer-objects state page-id)]
|
||||
objects))
|
||||
st/state =)))
|
||||
|
||||
(defn- use-objects [from]
|
||||
(let [page-objects-ref
|
||||
(mf/with-memo [from]
|
||||
(if (= from :workspace)
|
||||
;; FIXME: fix naming consistency issues
|
||||
refs/workspace-page-objects
|
||||
(refs/get-viewer-objects)))]
|
||||
(get-viewer-objects)))]
|
||||
(mf/deref page-objects-ref)))
|
||||
|
||||
(defn- shapes->images
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.shape-icon :as sir]
|
||||
|
|
|
@ -267,7 +267,8 @@
|
|||
(when (= flow-id (:id current-flow))
|
||||
[:span {:class (stl/css :icon)} i/tick])])]]])))
|
||||
|
||||
(mf/defc interactions-menu
|
||||
(mf/defc interactions-menu*
|
||||
{::mf/props :obj}
|
||||
[{:keys [interactions-mode]}]
|
||||
(let [show-dropdown? (mf/use-state false)
|
||||
toggle-dropdown (mf/use-fn #(swap! show-dropdown? not))
|
||||
|
@ -281,6 +282,7 @@
|
|||
(keyword))]
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dv/set-interactions-mode mode)))))]
|
||||
|
||||
[:div {:on-click toggle-dropdown
|
||||
:class (stl/css :view-options)}
|
||||
[:span {:class (stl/css :dropdown-title)} (tr "viewer.header.interactions")]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue