mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 22:10:34 +00:00
✨ Tell a library branch that its consumers read main (#12570)
Files that use a shared library read its main, never a branch, so a branch of a library is not isolated in the sense the create dialog promised. On a shared library the create dialog now says that files using the library can adopt changes from main only, the chip of a library branch says the same, and the note beside main reads "Default branch (read by library consumers)". Other files keep their copy. get-file-branch-info returns source-is-shared so a branch knows its main is a library. AI-assisted-by: claude-opus-5-5 Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
This commit is contained in:
parent
d613972474
commit
d4f999e6dd
@ -1733,7 +1733,8 @@
|
||||
fb.base_snapshot_id,
|
||||
bf.revn AS branch_revn,
|
||||
sf.revn AS source_revn,
|
||||
sf.name AS source_name
|
||||
sf.name AS source_name,
|
||||
sf.is_shared AS source_is_shared
|
||||
FROM file_branch AS fb
|
||||
JOIN file AS bf ON (bf.id = fb.branch_file_id)
|
||||
JOIN file AS sf ON (sf.id = fb.source_file_id)
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
{
|
||||
"~: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,
|
||||
"~:can-read": true,
|
||||
"~:is-logged": true
|
||||
},
|
||||
"~:has-media-trimmed": false,
|
||||
"~:comment-thread-seqn": 0,
|
||||
"~:name": "New File 1",
|
||||
"~:revn": 11,
|
||||
"~:modified-at": "~m1713873823633",
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:is-shared": true,
|
||||
"~:version": 46,
|
||||
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:created-at": "~m1713536343369",
|
||||
"~:data": {
|
||||
"~:pages": [
|
||||
"~u66697432-c33d-8055-8006-2c62cc084cad"
|
||||
],
|
||||
"~:pages-index": {
|
||||
"~u66697432-c33d-8055-8006-2c62cc084cad": {
|
||||
"~#penpot/pointer": [
|
||||
"~ude58c8f6-c5c2-8196-8004-3df9e2e52d88",
|
||||
{
|
||||
"~:created-at": "~m1713873823636"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:options": {
|
||||
"~:components-v2": true
|
||||
},
|
||||
"~:recent-colors": [
|
||||
{
|
||||
"~:color": "#0000ff",
|
||||
"~:opacity": 1,
|
||||
"~:id": null,
|
||||
"~:file-id": null,
|
||||
"~:image": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
{
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38f280443998",
|
||||
"~:branch-file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:source-file-id": "~uc7ce0794-0992-8105-8004-38f280443997",
|
||||
"~:name": "checkout",
|
||||
"~:description": null,
|
||||
"~:status": "open",
|
||||
"~:created-by": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:created-at": "~m1713536343369",
|
||||
"~:updated-at": "~m1713873823633",
|
||||
"~:source-name": "New File 1",
|
||||
"~:ahead": 1,
|
||||
"~:behind": 0,
|
||||
"~:conflicts": 0,
|
||||
"~:source-is-shared": true
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38f280443998",
|
||||
"~:branch-file-id": "~uc7ce0794-0992-8105-8004-38f280443850",
|
||||
"~:source-file-id": "~uc7ce0794-0992-8105-8004-38f280443849",
|
||||
"~:name": "checkout",
|
||||
"~:description": null,
|
||||
"~:status": "open",
|
||||
"~:created-by": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:created-at": "~m1713536343369",
|
||||
"~:updated-at": "~m1713873823633",
|
||||
"~:source-name": "New File 1",
|
||||
"~:ahead": 1,
|
||||
"~:behind": 0,
|
||||
"~:conflicts": 0
|
||||
}
|
||||
]
|
||||
80
frontend/playwright/ui/specs/branches-library.spec.js
Normal file
80
frontend/playwright/ui/specs/branches-library.spec.js
Normal file
@ -0,0 +1,80 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../pages/WorkspacePage";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
await WorkspacePage.mockConfigFlags(page, ["enable-branching"]);
|
||||
});
|
||||
|
||||
const setupBranching = async (workspacePage, rpcs) => {
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockRPCs({
|
||||
"get-file-branch-info?file-id=*":
|
||||
"pull-requests/get-file-branch-info-none.json",
|
||||
"get-file-branches?file-id=*": "pull-requests/get-file-branches-empty.json",
|
||||
"get-file-pull-requests?file-id=*":
|
||||
"pull-requests/get-file-branches-empty.json",
|
||||
...rpcs,
|
||||
});
|
||||
};
|
||||
|
||||
test("on a branch of a library, the header says consumers read main", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await setupBranching(workspacePage, {
|
||||
"get-file-branch-info?file-id=*":
|
||||
"pull-requests/get-file-branch-info-library-branch.json",
|
||||
});
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
const chip = page.getByTitle(
|
||||
"On branch checkout from main. Library consumers can adopt changes from main only.",
|
||||
);
|
||||
await expect(chip).toBeVisible();
|
||||
|
||||
await chip.click();
|
||||
await expect(
|
||||
page.getByText("Default branch (read by library consumers)"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("on a library, the create dialog says consumers read main", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await setupBranching(workspacePage, {
|
||||
"get-file-branches?file-id=*": "pull-requests/get-file-branches-one.json",
|
||||
});
|
||||
await workspacePage.mockGetFile("pull-requests/get-file-blank-shared.json");
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
await expect(
|
||||
page.getByText("Default branch (read by library consumers)"),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByTitle("Switch branch").click();
|
||||
await page.getByText("New branch").click();
|
||||
await expect(
|
||||
page.getByText(
|
||||
"files that use this library can adopt changes from main only",
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("on an ordinary file, the create dialog keeps its note", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await setupBranching(workspacePage, {
|
||||
"get-file-branches?file-id=*": "pull-requests/get-file-branches-one.json",
|
||||
});
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
await expect(page.getByText("Default branch", { exact: true })).toBeVisible();
|
||||
|
||||
await page.getByTitle("Switch branch").click();
|
||||
await page.getByText("New branch").click();
|
||||
await expect(page.getByText("isolated copy")).toBeVisible();
|
||||
await expect(page.getByText("files that use this library")).toBeHidden();
|
||||
});
|
||||
@ -81,6 +81,14 @@
|
||||
(zero? (or (:behind row) 0))
|
||||
(zero? (or (:conflicts row) 0))))
|
||||
|
||||
(defn- main-subtitle
|
||||
"The note beside main. Files that use a shared library read its main,
|
||||
so on a library it says so."
|
||||
[library?]
|
||||
(if library?
|
||||
(tr "workspace.branches.main-subtitle-library")
|
||||
(tr "workspace.branches.main-subtitle")))
|
||||
|
||||
;; --- Popover
|
||||
|
||||
(mf/defc switcher-row*
|
||||
@ -92,7 +100,7 @@
|
||||
:data-branch (dm/str (:branch-file-id row))
|
||||
:on-click on-open}
|
||||
(if (:is-main row)
|
||||
(tr "workspace.branches.main-subtitle")
|
||||
(main-subtitle (:is-library row))
|
||||
[:> row-status* {:row row :pr pr}])]])
|
||||
|
||||
(mf/defc switcher-popover*
|
||||
@ -115,6 +123,7 @@
|
||||
(when-not (:is-main current)
|
||||
{:name (tr "workspace.branches.main")
|
||||
:is-main true
|
||||
:is-library (boolean (:source-is-shared current))
|
||||
:branch-file-id (:source-file-id current)})
|
||||
|
||||
rows
|
||||
@ -234,6 +243,7 @@
|
||||
(or ctx
|
||||
{:name (tr "workspace.branches.main")
|
||||
:is-main true
|
||||
:is-library (boolean (:is-shared file))
|
||||
:created-by (:id profile)
|
||||
:branch-file-id file-id})
|
||||
|
||||
@ -275,8 +285,16 @@
|
||||
[:div {:class (stl/css :branch-bar) :ref bar-ref}
|
||||
[:> branch-chip* {:label (:name current)
|
||||
:is-open open?
|
||||
:title (if (:is-main current)
|
||||
:title (cond
|
||||
(:is-main current)
|
||||
(tr "workspace.branches.switcher.tooltip")
|
||||
|
||||
;; files that use the library read main,
|
||||
;; never this branch
|
||||
(:source-is-shared current)
|
||||
(tr "workspace.branches.header-badge-tooltip-library" (:name current))
|
||||
|
||||
:else
|
||||
(tr "workspace.branches.header-badge-tooltip" (:name current)))
|
||||
:on-click on-toggle}]
|
||||
|
||||
@ -288,7 +306,7 @@
|
||||
[:> text* {:as "span"
|
||||
:typography t/body-small
|
||||
:class (stl/css :status-note)}
|
||||
(tr "workspace.branches.main-subtitle")]
|
||||
(main-subtitle (:is-shared file))]
|
||||
|
||||
^boolean merged?
|
||||
nil
|
||||
|
||||
@ -224,7 +224,9 @@
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :create-branch}
|
||||
[{:keys [file-name file-id]}]
|
||||
(let [branch-name* (mf/use-state "")
|
||||
(let [;; a branch only grows from main, so the open file is main
|
||||
library? (boolean (:is-shared (mf/deref refs/file)))
|
||||
branch-name* (mf/use-state "")
|
||||
description* (mf/use-state "")
|
||||
branch-name (deref branch-name*)
|
||||
description (deref description*)
|
||||
@ -279,7 +281,9 @@
|
||||
(tr "workspace.branches.create.derive-from-latest")]]]
|
||||
|
||||
[:> context-notification* {:level :info :type :context}
|
||||
(tr "workspace.branches.create.info")]]
|
||||
(if library?
|
||||
(tr "workspace.branches.create.info-library")
|
||||
(tr "workspace.branches.create.info"))]]
|
||||
|
||||
[:> modal-footer* {}
|
||||
[:> button* {:variant "secondary" :on-click on-close}
|
||||
|
||||
@ -6808,6 +6808,12 @@ msgstr ""
|
||||
"Your branch is a complete, isolated copy. Nothing you do affects main until "
|
||||
"you merge it."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs:283
|
||||
msgid "workspace.branches.create.info-library"
|
||||
msgstr ""
|
||||
"Your branch is a complete copy. Nothing you do affects main until you merge "
|
||||
"it, and files that use this library can adopt changes from main only."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs:283
|
||||
msgid "workspace.branches.create.name-label"
|
||||
msgstr "Branch name"
|
||||
@ -6849,6 +6855,10 @@ msgstr "No branches yet"
|
||||
msgid "workspace.branches.header-badge-tooltip"
|
||||
msgstr "On branch %s from main"
|
||||
|
||||
#: src/app/main/ui/workspace/branch_switcher.cljs:295
|
||||
msgid "workspace.branches.header-badge-tooltip-library"
|
||||
msgstr "On branch %s from main. Library consumers can adopt changes from main only."
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs:485
|
||||
msgid "workspace.branches.info.author"
|
||||
msgstr "Created by"
|
||||
@ -7070,6 +7080,10 @@ msgstr "Main has new changes. You can update this branch"
|
||||
msgid "workspace.branches.main-subtitle"
|
||||
msgstr "Default branch"
|
||||
|
||||
#: src/app/main/ui/workspace/branch_switcher.cljs:88
|
||||
msgid "workspace.branches.main-subtitle-library"
|
||||
msgstr "Default branch (read by library consumers)"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/branches.cljs:779
|
||||
msgid "workspace.branches.materialize.accept"
|
||||
msgstr "Turn it into a file"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user