mirror of
https://github.com/penpot/penpot.git
synced 2026-11-03 08:30:40 +00:00
✨ Publish the preview's limits in the branch panel
Every refusal the branching commands can emit now has a line a user can read: the three size gates name the limit they hit with the number the backend sent, the scope refusals (no branch of a branch, a released merge base, a change kind an update cannot replay, a branch whose state moved) each get their own message, and a Branching limits dialog reachable from the panel footer states them before anybody meets one. The three numbers come from ::get-branching-limits, so the copy keeps no second copy of a limit it could drift from.
This commit is contained in:
parent
264f1e930c
commit
a6e7972297
@ -50,9 +50,11 @@
|
||||
|
||||
(defonce default-state
|
||||
{:status :loading
|
||||
:data nil})
|
||||
:data nil
|
||||
:limits nil})
|
||||
|
||||
(declare fetch-branches)
|
||||
(declare fetch-branching-limits)
|
||||
(declare fetch-branch-context)
|
||||
(declare show-branches-panel)
|
||||
|
||||
@ -65,7 +67,8 @@
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (fetch-branches)))))
|
||||
(rx/of (fetch-branches)
|
||||
(fetch-branching-limits)))))
|
||||
|
||||
(defn update-branches-state
|
||||
[branch-state]
|
||||
@ -91,6 +94,57 @@
|
||||
(rx/catch (fn [_]
|
||||
(rx/of (update-branches-state {:status :loaded :data []})))))))))
|
||||
|
||||
(defn fetch-branching-limits
|
||||
"Load the size limits this instance refuses beyond, so the panel can
|
||||
state them before a user meets one. The numbers live in the backend
|
||||
config and are never restated here."
|
||||
[]
|
||||
(ptk/reify ::fetch-branching-limits
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when (nil? (get-in state [:workspace-branches :limits]))
|
||||
(->> (rp/cmd! :get-branching-limits {})
|
||||
(rx/map #(update-branches-state {:limits %}))
|
||||
(rx/catch (fn [_] (rx/empty))))))))
|
||||
|
||||
(defn- refusal-notification
|
||||
"The notification for a branching refusal, naming the limit it hit and
|
||||
what to do instead, or `fallback` when the cause is not one. The number
|
||||
comes from the refusal itself: the copy never keeps its own copy of a
|
||||
limit, so it cannot drift from the config the gate reads."
|
||||
[cause fallback]
|
||||
(let [{:keys [code limit]} (ex-data cause)]
|
||||
(case code
|
||||
:branching-shape-limit-exceeded
|
||||
(ntf/warn (tr "workspace.branches.limits.refuse-shapes" (str limit)))
|
||||
|
||||
:branching-page-limit-exceeded
|
||||
(ntf/warn (tr "workspace.branches.limits.refuse-pages" (str limit)))
|
||||
|
||||
:branching-oplog-limit-exceeded
|
||||
(ntf/warn (tr "workspace.branches.limits.refuse-changes" (str limit)))
|
||||
|
||||
:cannot-branch-a-branch
|
||||
(ntf/warn (tr "workspace.branches.limits.refuse-nested"))
|
||||
|
||||
:base-snapshot-missing
|
||||
(ntf/error (tr "workspace.branches.limits.refuse-base-missing"))
|
||||
|
||||
;; the branch changed state under an open panel: one line for the
|
||||
;; whole family, because what the user does next is the same
|
||||
(:branch-not-found :branch-merged :branch-not-open)
|
||||
(ntf/warn (tr "workspace.branches.limits.refuse-stale"))
|
||||
|
||||
:invalid-branch-name
|
||||
(ntf/warn (tr "workspace.branches.limits.refuse-name"))
|
||||
|
||||
;; the same refusal as the `:unsupported` merge status, raised while
|
||||
;; squashing an update, so it reads the same to a user
|
||||
:unsupported-update-squash
|
||||
(ntf/warn (tr "workspace.branches.update.unsupported"))
|
||||
|
||||
(ntf/error fallback))))
|
||||
|
||||
;; --- Dashboard popover (list a file's branches from the dashboard)
|
||||
|
||||
(defn- set-dashboard-branches
|
||||
@ -124,7 +178,7 @@
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :update-file-branch {:id id :name name})
|
||||
(rx/mapcat (fn [_] (rx/of (fetch-branches) (fetch-branch-context))))
|
||||
(rx/catch (fn [_] (rx/of (ntf/error (tr "workspace.branches.lifecycle.error")))))))))
|
||||
(rx/catch (fn [cause] (rx/of (refusal-notification cause (tr "workspace.branches.lifecycle.error")))))))))
|
||||
|
||||
(defn set-branch-description
|
||||
"Update a branch's free-text description (shown in the Branch info modal).
|
||||
@ -136,7 +190,7 @@
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :update-file-branch {:id id :description description})
|
||||
(rx/mapcat (fn [_] (rx/of (fetch-branches) (fetch-branch-context))))
|
||||
(rx/catch (fn [_] (rx/of (ntf/error (tr "workspace.branches.lifecycle.error")))))))))
|
||||
(rx/catch (fn [cause] (rx/of (refusal-notification cause (tr "workspace.branches.lifecycle.error")))))))))
|
||||
|
||||
(defn archive-branch
|
||||
([id] (archive-branch id true))
|
||||
@ -147,7 +201,7 @@
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :archive-file-branch {:id id :archived archived?})
|
||||
(rx/mapcat (fn [_] (rx/of (fetch-branches))))
|
||||
(rx/catch (fn [_] (rx/of (ntf/error (tr "workspace.branches.lifecycle.error"))))))))))
|
||||
(rx/catch (fn [cause] (rx/of (refusal-notification cause (tr "workspace.branches.lifecycle.error"))))))))))
|
||||
|
||||
(defn delete-branch
|
||||
[id]
|
||||
@ -158,7 +212,7 @@
|
||||
(->> (rp/cmd! :delete-file-branch {:id id})
|
||||
(rx/mapcat (fn [_] (rx/of (ntf/success (tr "workspace.branches.lifecycle.deleted"))
|
||||
(fetch-branches))))
|
||||
(rx/catch (fn [_] (rx/of (ntf/error (tr "workspace.branches.lifecycle.error")))))))))
|
||||
(rx/catch (fn [cause] (rx/of (refusal-notification cause (tr "workspace.branches.lifecycle.error")))))))))
|
||||
|
||||
(defn create-branch
|
||||
"Create a branch from a file. With no `file-id`, branches the currently
|
||||
@ -212,8 +266,8 @@
|
||||
;; from dashboard: jump into the new branch
|
||||
(rx/of (dcm/go-to-workspace :file-id branch-file-id))))))
|
||||
(rx/catch
|
||||
(fn [_]
|
||||
(rx/of (ntf/error (tr "workspace.branches.create.error"))))))))))))
|
||||
(fn [cause]
|
||||
(rx/of (refusal-notification cause (tr "workspace.branches.create.error"))))))))))))
|
||||
|
||||
(defn open-branch
|
||||
"Navigate to the branch file as a normal workspace file. Switching
|
||||
@ -381,8 +435,8 @@
|
||||
:conflicts (rx/of (modal/show :branch-conflicts {:branch branch :mode :update}))
|
||||
:unsupported (rx/of (ntf/warn (tr "workspace.branches.update.unsupported")))
|
||||
(rx/of (ntf/error (tr "workspace.branches.update.error"))))))
|
||||
(rx/catch (fn [_]
|
||||
(rx/of (ntf/error (tr "workspace.branches.update.error")))))))))))
|
||||
(rx/catch (fn [cause]
|
||||
(rx/of (refusal-notification cause (tr "workspace.branches.update.error")))))))))))
|
||||
|
||||
(defn set-conflict-resolution
|
||||
"Choose `:main` or `:branch` for a whole conflicting entity (by id). This
|
||||
@ -477,5 +531,5 @@
|
||||
;; user resolves against the current state
|
||||
(rx/of (ntf/warn (tr "workspace.branches.merge.main-moved"))
|
||||
(fetch-branch-diff branch-id))
|
||||
(rx/of (ntf/error (tr "workspace.branches.merge.error")))))))))))))
|
||||
(rx/of (refusal-notification cause (tr "workspace.branches.merge.error")))))))))))))
|
||||
|
||||
|
||||
@ -316,6 +316,77 @@
|
||||
:on-click on-submit}
|
||||
(tr "workspace.branches.create.submit")]]]]))
|
||||
|
||||
;; --- Preview limits dialog (modal)
|
||||
;;
|
||||
;; Every refusal the branching commands can emit has a line here, so a user
|
||||
;; who meets one reads the feature as scoped rather than as broken. The three
|
||||
;; numbers are fetched from the backend (`::get-branching-limits`) and never
|
||||
;; restated in the copy: the config the gates read is their one home.
|
||||
|
||||
(mf/defc limits-dialog*
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :branch-limits}
|
||||
[]
|
||||
(let [{:keys [limits]} (mf/deref branches)
|
||||
|
||||
on-close
|
||||
(mf/use-fn #(st/emit! (modal/hide)))
|
||||
|
||||
item
|
||||
(mf/use-fn
|
||||
(fn [text]
|
||||
[:li {:class (stl/css :limits-item) :key text} text]))]
|
||||
|
||||
(mf/with-effect []
|
||||
(st/emit! (dwb/fetch-branching-limits)))
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:div {:class (stl/css :modal-header-icon)}
|
||||
[:> i/icon* {:icon-id i/git-branch}]]
|
||||
[:div {:class (stl/css :modal-header-text)}
|
||||
[:h2 {:class (stl/css :modal-title)} (tr "workspace.branches.limits.title")]
|
||||
[:span {:class (stl/css :modal-subtitle)} (tr "workspace.branches.limits.subtitle")]]
|
||||
[:> button* {:variant "ghost"
|
||||
:icon i/close
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click on-close}]]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :limits-section)}
|
||||
[:h3 {:class (stl/css :limits-section-title)} (tr "workspace.branches.limits.size.title")]
|
||||
[:ul {:class (stl/css :limits-list)}
|
||||
(if (some? limits)
|
||||
[:*
|
||||
(item (tr "workspace.branches.limits.size.shapes" (dm/str (:max-shapes limits))))
|
||||
(item (tr "workspace.branches.limits.size.pages" (dm/str (:max-pages limits))))
|
||||
(item (tr "workspace.branches.limits.size.changes" (dm/str (:max-oplog-changes limits))))]
|
||||
(item (tr "workspace.branches.limits.size.loading")))]]
|
||||
|
||||
[:div {:class (stl/css :limits-section)}
|
||||
[:h3 {:class (stl/css :limits-section-title)} (tr "workspace.branches.limits.scope.title")]
|
||||
[:ul {:class (stl/css :limits-list)}
|
||||
(item (tr "workspace.branches.limits.scope.nested"))
|
||||
(item (tr "workspace.branches.limits.scope.libraries"))
|
||||
(item (tr "workspace.branches.limits.scope.comments"))
|
||||
(item (tr "workspace.branches.limits.scope.page-attrs"))]]
|
||||
|
||||
[:div {:class (stl/css :limits-section)}
|
||||
[:h3 {:class (stl/css :limits-section-title)} (tr "workspace.branches.limits.merge.title")]
|
||||
[:ul {:class (stl/css :limits-list)}
|
||||
(item (tr "workspace.branches.limits.merge.whole-entities"))
|
||||
(item (tr "workspace.branches.limits.merge.no-partial"))
|
||||
(item (tr "workspace.branches.limits.merge.main-moved"))
|
||||
(item (tr "workspace.branches.limits.merge.base-missing"))]]
|
||||
|
||||
[:> context-notification* {:level :info :type :context}
|
||||
(tr "workspace.branches.limits.exit")]]
|
||||
|
||||
[:div {:class (stl/css :modal-footer)}
|
||||
[:> button* {:variant "primary" :on-click on-close}
|
||||
(tr "labels.close")]]]]))
|
||||
|
||||
;; --- Merge confirmation dialog (modal)
|
||||
|
||||
(mf/defc merge-branch-dialog*
|
||||
@ -863,7 +934,10 @@
|
||||
(mf/use-fn
|
||||
(mf/deps file)
|
||||
(fn [_]
|
||||
(modal/show! :create-branch {:file-name (:name file)})))]
|
||||
(modal/show! :create-branch {:file-name (:name file)})))
|
||||
|
||||
on-show-limits
|
||||
(mf/use-fn #(modal/show! :branch-limits {}))]
|
||||
|
||||
(mf/with-effect []
|
||||
(st/emit! (dwb/init-branches-state))
|
||||
@ -932,7 +1006,15 @@
|
||||
:entry entry
|
||||
:profiles profiles
|
||||
:menu-open? (= open-menu k)
|
||||
:on-set-menu (partial set-menu k)}]))])])])]))
|
||||
:on-set-menu (partial set-menu k)}]))])])])
|
||||
|
||||
;; the preview's limits belong where the feature is used, not in the
|
||||
;; release notes: a user who meets a refusal finds the reason here
|
||||
[:div {:class (stl/css :branches-footer)}
|
||||
[:button {:class (stl/css :branches-limits-link)
|
||||
:on-click on-show-limits}
|
||||
[:> i/icon* {:icon-id i/help :size "s"}]
|
||||
[:span (tr "workspace.branches.limits.link")]]]]))
|
||||
|
||||
;; --- Compare changes dialog (read-only 3-way diff)
|
||||
|
||||
|
||||
@ -1522,3 +1522,56 @@
|
||||
.branches-archived-count {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
.branches-footer {
|
||||
margin-block-start: auto;
|
||||
padding: var(--sp-s);
|
||||
border-block-start: 1px solid var(--color-background-quaternary);
|
||||
}
|
||||
|
||||
.branches-limits-link {
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-xs);
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
color: var(--color-foreground-secondary);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.limits-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
margin-block-end: var(--sp-m);
|
||||
}
|
||||
|
||||
.limits-section-title {
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
color: var(--color-foreground-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.limits-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
margin: 0;
|
||||
padding-inline-start: var(--sp-l);
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.limits-item {
|
||||
@include t.use-typography("body-small");
|
||||
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
@ -6809,6 +6809,134 @@ msgstr "Branch deleted"
|
||||
msgid "workspace.branches.lifecycle.error"
|
||||
msgstr "The branch operation could not be completed"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.link"
|
||||
msgstr "What branching can and can't do yet"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.title"
|
||||
msgstr "Branching limits"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.subtitle"
|
||||
msgstr "What this technology preview refuses, and what to do instead"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.size.title"
|
||||
msgstr "Size"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.size.shapes"
|
||||
msgstr ""
|
||||
"A file with more than %s shapes can't be branched. Split the file, or keep "
|
||||
"working on it without branches."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.size.pages"
|
||||
msgstr "A file with more than %s pages can't be branched."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.size.changes"
|
||||
msgstr ""
|
||||
"A branch that has collected more than %s changes can't be compared or "
|
||||
"merged. Merge it, or turn it into an ordinary file."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.size.loading"
|
||||
msgstr "Loading this instance's limits…"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.scope.title"
|
||||
msgstr "Out of scope in this preview"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.scope.nested"
|
||||
msgstr "A branch can't be branched again."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.scope.libraries"
|
||||
msgstr ""
|
||||
"A branch covers one file. Changes a shared library makes elsewhere aren't "
|
||||
"branched with it."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.scope.comments"
|
||||
msgstr "Comments and comment threads stay on the file they were written on."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.scope.page-attrs"
|
||||
msgstr ""
|
||||
"A few page settings are reported as changed but not merged. The comparison "
|
||||
"names them so nothing is lost silently."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.merge.title"
|
||||
msgstr "What a merge won't decide for you"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.merge.whole-entities"
|
||||
msgstr ""
|
||||
"A conflict is resolved one element at a time: you choose this branch's "
|
||||
"version or main's."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.merge.no-partial"
|
||||
msgstr "A merge takes the whole branch. There's no partial merge yet."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.merge.main-moved"
|
||||
msgstr ""
|
||||
"If main changes while you're resolving, the comparison is reloaded and you "
|
||||
"review it again."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.merge.base-missing"
|
||||
msgstr ""
|
||||
"If the point this branch started from is no longer available, the branch can "
|
||||
"still be turned into an ordinary file, but it can't be merged."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs
|
||||
msgid "workspace.branches.limits.exit"
|
||||
msgstr ""
|
||||
"Any branch can become an ordinary file, keeping its content. Nothing you "
|
||||
"make here depends on branching staying switched on."
|
||||
|
||||
#: src/app/main/data/workspace/branches.cljs
|
||||
msgid "workspace.branches.limits.refuse-shapes"
|
||||
msgstr ""
|
||||
"This file has more shapes than branching supports (%s). Split the file, or "
|
||||
"keep working on it without branches."
|
||||
|
||||
#: src/app/main/data/workspace/branches.cljs
|
||||
msgid "workspace.branches.limits.refuse-pages"
|
||||
msgstr "This file has more pages than branching supports (%s)."
|
||||
|
||||
#: src/app/main/data/workspace/branches.cljs
|
||||
msgid "workspace.branches.limits.refuse-changes"
|
||||
msgstr ""
|
||||
"This branch has collected more changes than a comparison supports (%s). "
|
||||
"Merge it, or turn it into an ordinary file."
|
||||
|
||||
#: src/app/main/data/workspace/branches.cljs
|
||||
msgid "workspace.branches.limits.refuse-nested"
|
||||
msgstr "A branch can't be branched again. Branch from main instead."
|
||||
|
||||
#: src/app/main/data/workspace/branches.cljs
|
||||
msgid "workspace.branches.limits.refuse-base-missing"
|
||||
msgstr ""
|
||||
"The point this branch started from is no longer available, so it can't be "
|
||||
"compared or merged. It can still become an ordinary file."
|
||||
|
||||
#: src/app/main/data/workspace/branches.cljs
|
||||
msgid "workspace.branches.limits.refuse-stale"
|
||||
msgstr ""
|
||||
"This branch is no longer in the state the panel showed. Reopen the branches "
|
||||
"panel to see where it is now."
|
||||
|
||||
#: src/app/main/data/workspace/branches.cljs
|
||||
msgid "workspace.branches.limits.refuse-name"
|
||||
msgstr "A branch name can't be empty."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs:799
|
||||
msgid "workspace.branches.loading"
|
||||
msgstr "Loading branches…"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user