diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj
index 784e52484..3d2098712 100644
--- a/backend/src/app/rpc/commands/files.clj
+++ b/backend/src/app/rpc/commands/files.clj
@@ -384,8 +384,10 @@
f.revn,
f.vern,
f.is_shared,
- ft.media_id AS thumbnail_id
+ ft.media_id AS thumbnail_id,
+ p.team_id
from file as f
+ inner join project as p on (p.id = f.project_id)
left join file_thumbnail as ft on (ft.file_id = f.id
and ft.revn = f.revn
and ft.deleted_at is null)
@@ -539,7 +541,8 @@
f.modified_at,
f.name,
f.is_shared,
- ft.media_id
+ ft.media_id,
+ p.team_id
from file as f
inner join project as p on (p.id = f.project_id)
left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn and ft.deleted_at is null)
@@ -686,7 +689,8 @@
f.name,
f.is_shared,
ft.media_id AS thumbnail_id,
- row_number() over w as row_num
+ row_number() over w as row_num,
+ p.team_id
from file as f
inner join project as p on (p.id = f.project_id)
left join file_thumbnail as ft on (ft.file_id = f.id
diff --git a/docs/img/styling/color-picker-gradient.webp b/docs/img/styling/color-picker-gradient.webp
new file mode 100644
index 000000000..eec479e70
Binary files /dev/null and b/docs/img/styling/color-picker-gradient.webp differ
diff --git a/docs/user-guide/styling/index.njk b/docs/user-guide/styling/index.njk
index 928b4d351..47b3d9df8 100644
--- a/docs/user-guide/styling/index.njk
+++ b/docs/user-guide/styling/index.njk
@@ -51,13 +51,24 @@ title: 06· Styling
Eyedropper - Allows you to pick any color of the objects at the viewport.
Color profiles - Select between RGB, the Harmony Wheel or HSV.
- Color type - Solid, linear gradient, radial gradient or image.
+ Color type - Solid, gradient, or image.
Sliders - Easily manage settings like brightness, saturation or opacity.
Values - Set precise color values of red(R), green(G), blue(B) and transparency(A).
Libraries - Switch between recent colors and libraries.
Color palette - A quick launcher of the palette with the selected library.
+Gradients
+You can apply gradient fills to layers. To do that select the Gradient type at the color picker.
+
+
+
+You can choose between two types of gradients:
+
+ Linear Gradient: A smooth transition between two or more colors along a straight line, with the option to adjust the angle.
+ Radial Gradient: A circular color transition that starts with one color at the center and gradually shifts to another at the edges, which could be a different color or a fade to transparency.
+
+
Color palette
The color palette allows you to have a selected color library in plain sight.
diff --git a/frontend/playwright/data/workspace/get-file-10113.json b/frontend/playwright/data/workspace/get-file-10113.json
new file mode 100644
index 000000000..c85e21966
--- /dev/null
+++ b/frontend/playwright/data/workspace/get-file-10113.json
@@ -0,0 +1,115 @@
+{
+ "~:features": {
+ "~#set": [
+ "layout/grid",
+ "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": "10113 - Emtpy lib",
+ "~:revn": 1,
+ "~:modified-at": "~m1739365936352",
+ "~:vern": 0,
+ "~:id": "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2e",
+ "~:is-shared": false,
+ "~:migrations": {
+ "~#ordered-set": [
+ "legacy-2",
+ "legacy-3",
+ "legacy-5",
+ "legacy-6",
+ "legacy-7",
+ "legacy-8",
+ "legacy-9",
+ "legacy-10",
+ "legacy-11",
+ "legacy-12",
+ "legacy-13",
+ "legacy-14",
+ "legacy-16",
+ "legacy-17",
+ "legacy-18",
+ "legacy-19",
+ "legacy-25",
+ "legacy-26",
+ "legacy-27",
+ "legacy-28",
+ "legacy-29",
+ "legacy-31",
+ "legacy-32",
+ "legacy-33",
+ "legacy-34",
+ "legacy-36",
+ "legacy-37",
+ "legacy-38",
+ "legacy-39",
+ "legacy-40",
+ "legacy-41",
+ "legacy-42",
+ "legacy-43",
+ "legacy-44",
+ "legacy-45",
+ "legacy-46",
+ "legacy-47",
+ "legacy-48",
+ "legacy-49",
+ "legacy-50",
+ "legacy-51",
+ "legacy-52",
+ "legacy-53",
+ "legacy-54",
+ "legacy-55",
+ "legacy-56",
+ "legacy-57",
+ "legacy-59",
+ "legacy-62",
+ "legacy-65",
+ "legacy-66",
+ "legacy-67"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u1ad2931c-eb80-8098-8005-b86c1d9d26c2",
+ "~:created-at": "~m1739365911709",
+ "~:data": {
+ "~:pages": [
+ "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2f"
+ ],
+ "~:pages-index": {
+ "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2f": {
+ "~#penpot/pointer": [
+ "~u5b7ebd2b-2907-80db-8005-b9d67c21cbd3",
+ {
+ "~:created-at": "~m1739365911687"
+ }
+ ]
+ }
+ },
+ "~:id": "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2e",
+ "~:options": {
+ "~:components-v2": true
+ },
+ "~:colors": {
+ "~u84a1567d-3f0f-804e-8005-b9d6907e3c8a": {
+ "~:path": "",
+ "~:color": "#0087ff",
+ "~:name": "#0087ff",
+ "~:modified-at": "~m1739365936355",
+ "~:opacity": 1,
+ "~:id": "~u84a1567d-3f0f-804e-8005-b9d6907e3c8a"
+ }
+ }
+ }
+}
diff --git a/frontend/playwright/data/workspace/get-file-fragment-10113.json b/frontend/playwright/data/workspace/get-file-fragment-10113.json
new file mode 100644
index 000000000..c37908a60
--- /dev/null
+++ b/frontend/playwright/data/workspace/get-file-fragment-10113.json
@@ -0,0 +1,101 @@
+{
+ "~:id": "~u5b7ebd2b-2907-80db-8005-b9d67c21cbd3",
+ "~:file-id": "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2e",
+ "~:created-at": "~m1739365911680",
+ "~:data": {
+ "~:options": {},
+ "~:objects": {
+ "~u00000000-0000-0000-0000-000000000000": {
+ "~#shape": {
+ "~:y": 0,
+ "~:hide-fill-on-export": false,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:rotation": 0,
+ "~:name": "Root Frame",
+ "~:width": 0.01,
+ "~:type": "~:frame",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u00000000-0000-0000-0000-000000000000",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 0,
+ "~:proportion": 1.0,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 0,
+ "~:y": 0,
+ "~:width": 0.01,
+ "~:height": 0.01,
+ "~:x1": 0,
+ "~:y1": 0,
+ "~:x2": 0.01,
+ "~:y2": 0.01
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#FFFFFF",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": []
+ }
+ }
+ },
+ "~:id": "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2f",
+ "~:name": "Page 1"
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/workspace/get-team-shared-files-empty.json b/frontend/playwright/data/workspace/get-team-shared-files-empty.json
new file mode 100644
index 000000000..0637a088a
--- /dev/null
+++ b/frontend/playwright/data/workspace/get-team-shared-files-empty.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/frontend/playwright/data/workspace/set-file-shared-10113.json b/frontend/playwright/data/workspace/set-file-shared-10113.json
new file mode 100644
index 000000000..9ff97cb34
--- /dev/null
+++ b/frontend/playwright/data/workspace/set-file-shared-10113.json
@@ -0,0 +1,5 @@
+{
+ "~:id": "~u5b7ebd2b-2907-80db-8005-b9d67c20cf2e",
+ "~:name": "10113 - Emtpy lib",
+ "~:is-shared": true
+}
diff --git a/frontend/playwright/ui/pages/WorkspacePage.js b/frontend/playwright/ui/pages/WorkspacePage.js
index 20b694f92..cdc6e2df8 100644
--- a/frontend/playwright/ui/pages/WorkspacePage.js
+++ b/frontend/playwright/ui/pages/WorkspacePage.js
@@ -227,7 +227,7 @@ export class WorkspacePage extends BaseWebSocketPage {
}
async openLibrariesModal(clickOptions = {}) {
- await this.sidebar.getByText("Libraries").click(clickOptions);
+ await this.sidebar.getByTestId("libraries").click(clickOptions);
await expect(this.librariesModal).toBeVisible();
}
diff --git a/frontend/playwright/ui/specs/workspace-shared-library.spec.js b/frontend/playwright/ui/specs/workspace-shared-library.spec.js
index 89c53cb7c..eaef180ae 100644
--- a/frontend/playwright/ui/specs/workspace-shared-library.spec.js
+++ b/frontend/playwright/ui/specs/workspace-shared-library.spec.js
@@ -72,3 +72,41 @@ test("Bug 9056 - 'More info' doesn't open the update tab", async ({ page }) => {
/library updates/i,
);
});
+
+test("Bug 10113 - Empty library modal for non-empty library", async ({
+ page,
+}) => {
+ const workspace = new WorkspacePage(page);
+
+ await workspace.setupEmptyFile(page);
+ await workspace.mockRPC(/get\-file\?/, "workspace/get-file-10113.json");
+ await workspace.mockRPC(
+ "get-file-fragment?file-id=*&fragment-id=*",
+ "workspace/get-file-fragment-10113.json",
+ );
+ await workspace.mockRPC(/get\-file\?/, "workspace/get-file-10113.json");
+ await workspace.mockRPC(
+ "get-team-shared-files?team-id=*",
+ "workspace/get-team-shared-files-empty.json",
+ );
+ await workspace.mockRPC(
+ "set-file-shared",
+ "workspace/set-file-shared-10113.json",
+ );
+
+ await workspace.goToWorkspace({
+ fileId: "5b7ebd2b-2907-80db-8005-b9d67c20cf2e",
+ pageId: "5b7ebd2b-2907-80db-8005-b9d67c20cf2f",
+ });
+
+ await workspace.clickAssets();
+ await workspace.openLibrariesModal();
+
+ await workspace.librariesModal
+ .getByRole("button", { name: "Publish" })
+ .click();
+
+ await expect(
+ workspace.page.getByText("Publish empty library"),
+ ).not.toBeVisible();
+});
diff --git a/frontend/src/app/main/data/common.cljs b/frontend/src/app/main/data/common.cljs
index cd8e73186..064d1901d 100644
--- a/frontend/src/app/main/data/common.cljs
+++ b/frontend/src/app/main/data/common.cljs
@@ -84,7 +84,7 @@
:controls :inline-actions
:type :inline
:level level
- :accept {:label (tr "Refresh")
+ :accept {:label (tr "labels.refresh")
:callback force-reload!}
:tag :notification))
diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs
index 5e637cc0f..acd082a20 100644
--- a/frontend/src/app/main/data/dashboard.cljs
+++ b/frontend/src/app/main/data/dashboard.cljs
@@ -16,6 +16,7 @@
[app.main.data.common :as dcm]
[app.main.data.event :as ev]
[app.main.data.fonts :as df]
+ [app.main.data.helpers :as dsh]
[app.main.data.modal :as modal]
[app.main.data.websocket :as dws]
[app.main.features :as features]
@@ -248,15 +249,18 @@
(ptk/reify ::create-project
ptk/WatchEvent
(watch [_ state _]
- (let [unames (cfh/get-used-names (get state :projects))
+ (let [team-id (:current-team-id state)
+ projects (dsh/lookup-team-projects state team-id)
+ unames (cfh/get-used-names projects)
base-name (tr "dashboard.new-project-prefix")
- name (cfh/generate-unique-name base-name unames :immediate-suffix? true)
- team-id (:current-team-id state)
- params {:name name
- :team-id team-id}
+ name (cfh/generate-unique-name base-name unames :immediate-suffix? true)
+ team-id (:current-team-id state)
+ params {:name name
+ :team-id team-id}
{:keys [on-success on-error]
:or {on-success identity
- on-error rx/throw}} (meta params)]
+ on-error rx/throw}}
+ (meta params)]
(->> (rp/cmd! :create-project params)
(rx/tap on-success)
(rx/map project-created)
@@ -465,10 +469,11 @@
ptk/UpdateEvent
(update [_ state]
- (-> state
- (assoc-in [:files id] file)
- (assoc-in [:recent-files id] file)
- (update-in [:projects project-id :count] inc)))))
+ (let [file (dissoc file :data)]
+ (-> state
+ (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}]
@@ -482,8 +487,11 @@
(watch [it state _]
(let [{:keys [on-success on-error]
:or {on-success identity
- on-error rx/throw}} (meta params)
- unames (cfh/get-used-names (get state :files))
+ on-error rx/throw}}
+ (meta params)
+
+ files (dsh/lookup-team-files state)
+ unames (cfh/get-used-names files)
base-name (tr "dashboard.new-file-prefix")
name (or name
(cfh/generate-unique-name base-name unames :immediate-suffix? true))
@@ -597,10 +605,10 @@
pparams (:path-params route)
in-project? (contains? pparams :project-id)
name (if in-project?
- (let [files (get state :files)
+ (let [files (dsh/lookup-team-files state team-id)
unames (cfh/get-used-names files)]
(cfh/generate-unique-name (tr "dashboard.new-file-prefix") unames :immediate-suffix? true))
- (let [projects (get state :projects)
+ (let [projects (dsh/lookup-team-projects state team-id)
unames (cfh/get-used-names projects)]
(cfh/generate-unique-name (tr "dashboard.new-project-prefix") unames :immediate-suffix? true)))
params (if in-project?
diff --git a/frontend/src/app/main/data/helpers.cljs b/frontend/src/app/main/data/helpers.cljs
index b104af0dc..279e3e0b2 100644
--- a/frontend/src/app/main/data/helpers.cljs
+++ b/frontend/src/app/main/data/helpers.cljs
@@ -168,3 +168,21 @@
[state]
(when-let [{:keys [x y width height]} (get-in state [:workspace-local :vbox])]
(gpt/point (+ x (/ width 2)) (+ y (/ height 2)))))
+
+(defn lookup-team-files
+ ([state]
+ (lookup-team-files state (:current-team-id state)))
+ ([state team-id]
+ (->> state
+ :files
+ (filter #(= team-id (:team-id (val %))))
+ (into {}))))
+
+(defn lookup-team-projects
+ ([state]
+ (lookup-team-projects (:current-team-id state)))
+ ([state team-id]
+ (->> state
+ :projects
+ (filter #(= team-id (:team-id (val %))))
+ (into {}))))
diff --git a/frontend/src/app/main/data/team.cljs b/frontend/src/app/main/data/team.cljs
index 7ffb46463..5ff7cf097 100644
--- a/frontend/src/app/main/data/team.cljs
+++ b/frontend/src/app/main/data/team.cljs
@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
+ [app.common.exceptions :as ex]
[app.common.logging :as log]
[app.common.schema :as sm]
[app.common.types.team :as ctt]
@@ -118,8 +119,10 @@
(let [team-id (:current-team-id state)
teams (get state :teams)
team (get teams team-id)]
- (rx/of (set-current-team team)
- (fetch-members))))))
+ (if (not team)
+ (rx/throw (ex/error :type :authentication))
+ (rx/of (set-current-team team)
+ (fetch-members)))))))
(defn initialize-team
[team-id]
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index 319c7f4ea..a23c08447 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -319,8 +319,6 @@
ptk/UpdateEvent
(update [_ state]
(-> state
- (dissoc :files)
- (dissoc :workspace-ready)
(assoc :recent-colors (:recent-colors storage/user))
(assoc :recent-fonts (:recent-fonts storage/user))
(assoc :current-file-id file-id)
@@ -395,12 +393,9 @@
(dissoc
:current-file-id
:workspace-editor-state
- :files
:workspace-media-objects
:workspace-persistence
:workspace-presence
- :workspace-tokens
- :workspace-ready
:workspace-undo)
(update :workspace-global dissoc :read-only?)
(assoc-in [:workspace-global :options-mode] :design)))
@@ -428,18 +423,18 @@
(defmethod ptk/resolve ::reload-current-file [_ _] (reload-current-file))
(defn initialize-page
- [page-id]
- (assert (uuid? page-id) "expected valid uuid for `page-id`")
+ [file-id page-id]
+ (assert (uuid? file-id) "expected valid uuid for `file-id`")
(ptk/reify ::initialize-page
ptk/UpdateEvent
(update [_ state]
- (if-let [{:keys [id] :as page} (dsh/lookup-page state page-id)]
+ (if-let [page (dsh/lookup-page state file-id page-id)]
;; we maintain a cache of page state for user convenience with the exception of the
;; selection; when user abandon the current page, the selection is lost
- (let [local (dm/get-in state [:workspace-cache id] default-workspace-local)]
+ (let [local (dm/get-in state [:workspace-cache [file-id page-id]] default-workspace-local)]
(-> state
- (assoc :current-page-id id)
+ (assoc :current-page-id page-id)
(assoc :workspace-local (assoc local :selected (d/ordered-set)))
(assoc :workspace-trimmed-page (dm/select-keys page [:id :name]))
@@ -451,24 +446,25 @@
ptk/WatchEvent
(watch [_ state _]
- (if (dsh/lookup-page state 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)))
- (rx/of (dcm/go-to-workspace))))))
+ (if (dsh/lookup-page state file-id page-id)
+ (rx/of (preload-data-uris page-id)
+ (dwth/watch-state-changes file-id page-id)
+ (dwl/watch-component-changes))
+ (rx/of (dcm/go-to-workspace :file-id file-id ::rt/replace true))))))
(defn finalize-page
- [page-id]
+ [file-id page-id]
+ (assert (uuid? file-id) "expected valid uuid for `file-id`")
(assert (uuid? page-id) "expected valid uuid for `page-id`")
+
(ptk/reify ::finalize-page
ptk/UpdateEvent
(update [_ state]
(let [local (-> (:workspace-local state)
(dissoc :edition :edit-path :selected))
- exit? (not= :workspace (dm/get-in state [:route :data :name]))
+ exit? (not= :workspace (rt/lookup-name state))
state (-> state
- (update :workspace-cache assoc page-id local)
+ (update :workspace-cache assoc [file-id page-id] local)
(dissoc :current-page-id
:workspace-local
:workspace-trimmed-page
diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs
index 95d1df358..2cf8ad292 100644
--- a/frontend/src/app/main/data/workspace/libraries.cljs
+++ b/frontend/src/app/main/data/workspace/libraries.cljs
@@ -741,12 +741,12 @@
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/filter (ptk/type? ::dw/initialize-page))
(rx/take 1)
(rx/observe-on :async)
- (rx/mapcat (fn [_] (select-and-zoom shape-id))))))]
+ (rx/mapcat (fn [_] (select-and-zoom shape-id))))
+ (rx/of (dcm/go-to-workspace :page-id page-id))))]
(when-let [component (dm/get-in data [:components id])]
(let [page-id (:main-instance-page component)
diff --git a/frontend/src/app/main/router.cljs b/frontend/src/app/main/router.cljs
index 58e421983..e4c3b16a1 100644
--- a/frontend/src/app/main/router.cljs
+++ b/frontend/src/app/main/router.cljs
@@ -120,6 +120,11 @@
([id params & {:as options}]
(navigate id params options)))
+(defn lookup-name
+ [state]
+ (dm/get-in state [:route :data :name]))
+
+;; FIXME: rename to lookup-params
(defn get-params
[state]
(dm/get-in state [:route :params :query]))
diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs
index 328f2b3e4..f363ea878 100644
--- a/frontend/src/app/main/ui.cljs
+++ b/frontend/src/app/main/ui.cljs
@@ -127,7 +127,6 @@
{::mf/props :obj
::mf/private true}
[{:keys [team-id children]}]
-
(mf/with-effect [team-id]
(st/emit! (dtm/initialize-team team-id))
(fn []
diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs
index c41e4273f..6e0d1da30 100644
--- a/frontend/src/app/main/ui/comments.cljs
+++ b/frontend/src/app/main/ui/comments.cljs
@@ -237,9 +237,14 @@
(str/last-index-of (subs node-text 0 offset) "@")
mention-text
- (subs node-text current-at-symbol)]
+ (subs node-text current-at-symbol)
- (if (re-matches #"@\w*" mention-text)
+ at-symbol-inside-word?
+ (and (> current-at-symbol 0)
+ (str/word? (str/slice node-text (- current-at-symbol 1) current-at-symbol)))]
+
+ (if (and (not at-symbol-inside-word?)
+ (re-matches #"@\w*" mention-text))
(do
(reset! cur-mention mention-text)
(rx/push! mentions-s {:type :display-mentions})
@@ -305,6 +310,17 @@
(when (fn? on-change)
(on-change (parse-nodes node))))))))
+ handle-insert-at-symbol
+ (mf/use-fn
+ (fn []
+ (let [node (mf/ref-val local-ref) [span-node] (current-text-node node)]
+ (when span-node
+ (let [node-text (dom/get-text span-node)
+ at-symbol (if (blank-content? node-text) "@" " @")]
+
+ (dom/set-html! span-node (str/concat node-text at-symbol))
+ (wapi/set-cursor-after! span-node))))))
+
handle-key-down
(mf/use-fn
(mf/deps on-esc on-ctrl-enter handle-select handle-input)
@@ -386,6 +402,8 @@
(case type
:insert-mention
(handle-insert-mention data)
+ :insert-at-symbol
+ (handle-insert-at-symbol)
nil))))))
@@ -521,15 +539,25 @@
{::mf/props :obj
::mf/private true}
[]
- (let [mentions-s (mf/use-ctx mentions-context)
+ (let [mentions-s (mf/use-ctx mentions-context)
display-mentions* (mf/use-state false)
- handle-mouse-down
+ handle-pointer-down
(mf/use-fn
+ (mf/deps @display-mentions*)
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
- (rx/push! mentions-s {:type :display-mentions})))]
+ (if @display-mentions*
+ (rx/push! mentions-s {:type :hide-mentions})
+ (rx/push! mentions-s {:type :insert-at-symbol}))))
+
+ handle-key-down
+ (mf/use-fn
+ (mf/deps @display-mentions*)
+ (fn [event]
+ (when (or (kbd/enter? event) (kbd/space? event))
+ (handle-pointer-down event))))]
(mf/use-effect
(fn []
@@ -545,8 +573,9 @@
[:> icon-button*
{:variant "ghost"
- :aria-label (tr "labels.options")
- :on-pointer-down handle-mouse-down
+ :aria-label (tr "labels.mention")
+ :on-pointer-down handle-pointer-down
+ :on-key-down handle-key-down
:icon-class (stl/css-case :open-mentions-button true
:is-toggled @display-mentions*)
:icon "at"}]))
diff --git a/frontend/src/app/main/ui/comments.scss b/frontend/src/app/main/ui/comments.scss
index 70fd5b793..8dda4e806 100644
--- a/frontend/src/app/main/ui/comments.scss
+++ b/frontend/src/app/main/ui/comments.scss
@@ -255,7 +255,6 @@
}
.open-mentions-button {
- cursor: pointer;
stroke: none;
fill: var(--color-foreground-secondary);
diff --git a/frontend/src/app/main/ui/settings/access_tokens.cljs b/frontend/src/app/main/ui/settings/access_tokens.cljs
index b555e47e5..198165076 100644
--- a/frontend/src/app/main/ui/settings/access_tokens.cljs
+++ b/frontend/src/app/main/ui/settings/access_tokens.cljs
@@ -149,7 +149,6 @@
[:input {:type "text"
:value (:token created "")
:class (stl/css :custom-input-token)
- :placeholder (tr "modals.create-access-token.token")
:read-only true}]
[:button {:title (tr "modals.create-access-token.copy-token")
:class (stl/css :copy-btn)
diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs
index a169ae616..24bf92e2e 100644
--- a/frontend/src/app/main/ui/workspace.cljs
+++ b/frontend/src/app/main/ui/workspace.cljs
@@ -9,7 +9,6 @@
(:require
[app.common.data.macros :as dm]
[app.main.data.common :as dcm]
- [app.main.data.helpers :as dsh]
[app.main.data.persistence :as dps]
[app.main.data.plugins :as dpl]
[app.main.data.workspace :as dw]
@@ -43,13 +42,6 @@
[okulary.core :as l]
[rumext.v2 :as mf]))
-(defn- make-workspace-ready-ref
- [file-id]
- (l/derived (fn [state]
- (and (= file-id (:workspace-ready state))
- (some? (dsh/lookup-file-data state file-id))))
- st/state))
-
(mf/defc workspace-content*
{::mf/private true}
[{:keys [file layout page wglobal]}]
@@ -138,22 +130,18 @@
key (events/listen globals/window "blur" focus-out)]
(partial events/unlistenByKey key)))
- (mf/with-effect [page-id]
- (if (some? page-id)
- (st/emit! (dw/initialize-page page-id))
- (st/emit! (dcm/go-to-workspace ::rt/replace true)))
-
+ (mf/with-effect [file page-id]
+ (st/emit! (dw/initialize-page (:id file) page-id))
(fn []
- (when (some? page-id)
- (st/emit! (dw/finalize-page page-id)))))
+ (when page-id
+ (st/emit! (dw/finalize-page (:id file) page-id)))))
(if (some? page)
[:> workspace-content* {:file file
:page page
:wglobal wglobal
:layout layout}]
- [:& workspace-loader*])))
-
+ [:> workspace-loader*])))
(def ^:private ref:file-without-data
(l/derived (fn [file]
@@ -181,10 +169,6 @@
read-only? (mf/deref refs/workspace-read-only?)
read-only? (or read-only? (not (:can-edit permissions)))
- ready* (mf/with-memo [file-id]
- (make-workspace-ready-ref file-id))
- ready? (mf/deref ready*)
-
design-tokens? (features/use-feature "design-tokens/v1")
background-color (:background-color wglobal)]
@@ -207,6 +191,10 @@
(st/emit! ::dps/force-persist
(dw/finalize-workspace file-id))))
+ (mf/with-effect [file page-id]
+ (when-not page-id
+ (st/emit! (dcm/go-to-workspace :file-id file-id ::rt/replace true))))
+
[:> (mf/provider ctx/current-project-id) {:value project-id}
[:> (mf/provider ctx/current-file-id) {:value file-id}
[:> (mf/provider ctx/current-page-id) {:value page-id}
@@ -219,9 +207,10 @@
:touch-action "none"}}
[:> context-menu*]
- (if ^boolean ready?
- [:> workspace-page* {:page-id page-id
- :file file
- :wglobal wglobal
- :layout layout}]
+ (if (some? file)
+ [:> workspace-page*
+ {:page-id page-id
+ :file file
+ :wglobal wglobal
+ :layout layout}]
[:> workspace-loader*])]]]]]]]))
diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs
index ce1daca56..5a07a1a25 100644
--- a/frontend/src/app/main/ui/workspace/libraries.cljs
+++ b/frontend/src/app/main/ui/workspace/libraries.cljs
@@ -14,7 +14,6 @@
[app.common.types.file :as ctf]
[app.common.types.typographies-list :as ctyl]
[app.common.uuid :as uuid]
- [app.config :as cf]
[app.main.data.dashboard :as dd]
[app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
@@ -176,15 +175,7 @@
(defn- empty-library?
"Check if currentt library summary has elements or not"
[summary]
- (let [colors (or (-> summary :colors :count) 0)
- components (or (-> summary :components :count) 0)
- media (or (-> summary :media :count) 0)
- typographies (or (-> summary :typographies :count) 0)]
-
- (and (zero? colors)
- (zero? components)
- (zero? media)
- (zero? typographies))))
+ (boolean (:is-empty summary)))
(mf/defc libraries-tab*
{::mf/props :obj
@@ -362,7 +353,7 @@
(nil? shared-libraries)
(tr "workspace.libraries.loading")
- (and (str/empty? search-term) (cf/external-feature-flag "templates-03" "test"))
+ (str/empty? search-term)
[:*
[:div {:class (stl/css :sample-libraries-info)}
(tr "workspace.libraries.empty.no-libraries")
@@ -377,19 +368,6 @@
{:library library
:importing importing*}])]]
- (str/empty? search-term)
- [:*
- [:span {:class (stl/css :empty-state-icon)}
- library-icon]
- (tr "workspace.libraries.no-shared-libraries-available")
- (when (cf/external-feature-flag "templates-01" "test")
- [:div {:class (stl/css :templates-info)}
- (tr "workspace.libraries.more-templates")
- [:a {:target "_blank"
- :class (stl/css :templates-info-link)
- :href "https://penpot.app/libraries-templates"}
- (tr "workspace.libraries.more-templates-link")]])]
-
:else
(tr "workspace.libraries.no-matches-for" search-term))]))]]))
diff --git a/frontend/src/app/main/ui/workspace/libraries.scss b/frontend/src/app/main/ui/workspace/libraries.scss
index a1df227fe..41a3747dd 100644
--- a/frontend/src/app/main/ui/workspace/libraries.scss
+++ b/frontend/src/app/main/ui/workspace/libraries.scss
@@ -326,16 +326,6 @@
padding: $s-0 $s-16;
}
-.templates-info {
- color: var(--color-accent-primary);
-}
-
-.templates-info-link {
- color: var(--color-accent-primary);
- text-decoration: underline;
- font-weight: $fw400;
-}
-
.sample-libraries-info {
display: flex;
flex-direction: column;
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
index 893ac6317..10973c9a5 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
@@ -8,7 +8,6 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
- [app.config :as cf]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.assets :as dwa]
@@ -92,12 +91,6 @@
reverse-sort? (= :desc ordering)
num-libs (count (mf/deref refs/libraries))
- show-templates-04-test1?
- (and (cf/external-feature-flag "templates-04" "test1") (zero? num-libs))
-
- show-templates-04-test2?
- (and (cf/external-feature-flag "templates-04" "test2") (zero? num-libs))
-
toggle-ordering
(mf/use-fn
(mf/deps ordering)
@@ -166,18 +159,12 @@
[:article {:class (stl/css :assets-bar)}
[:div {:class (stl/css :assets-header)}
(when-not ^boolean read-only?
- (cond
- show-templates-04-test1?
- [:button {:class (stl/css :libraries-button)
- :on-click show-libraries-dialog
- :data-testid "libraries"}
- (tr "workspace.assets.add-library")]
- show-templates-04-test2?
+ (if (= num-libs 1)
[:button {:class (stl/css :add-library-button)
:on-click show-libraries-dialog
:data-testid "libraries"}
(tr "workspace.assets.add-library")]
- :else
+
[:button {:class (stl/css :libraries-button)
:on-click show-libraries-dialog
:data-testid "libraries"}
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs
index bef71e37e..15f81726c 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets/components.cljs
@@ -534,7 +534,7 @@
:on-ungroup on-ungroup
:on-context-menu on-context-menu
:selected-full selected-full
- :local ^boolean is-local}])
+ :is-local ^boolean is-local}])
[:& cmm/assets-context-menu
{:on-close on-close-menu
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
index e223622b8..c3c2b68de 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
@@ -77,12 +77,12 @@
[:& radio-button {:icon i/boolean-intersection
:value "intersection"
:disabled disabled-bool-btns
- :title (str (tr "intersection") " (" (sc/get-tooltip :bool-intersection) ")")
+ :title (str (tr "workspace.shape.menu.intersection") " (" (sc/get-tooltip :bool-intersection) ")")
:id "bool-opt-intersection"}]
[:& radio-button {:icon i/boolean-exclude
:value "exclude"
:disabled disabled-bool-btns
- :title (str (tr "exclude") " (" (sc/get-tooltip :bool-exclude) ")")
+ :title (str (tr "workspace.shape.menu.exclude") " (" (sc/get-tooltip :bool-exclude) ")")
:id "bool-opt-exclude"}]]]
[:button
diff --git a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs
index c68c0287e..bfdd503fe 100644
--- a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs
+++ b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs
@@ -120,7 +120,7 @@
(dom/prevent-default e)
(dom/stop-propagation e)
(st/emit! (wdt/toggle-token-theme-active? group name)))}
- [:& switch {:name (tr "workspace.token.theme" name)
+ [:& switch {:name (tr "workspace.token.theme-name" name)
:on-change (constantly nil)
:selected? selected?}]]
[:> text* {:as "span" :typography "body-medium" :class (stl/css :theme-name)} name]]
diff --git a/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs b/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs
index 3d331c616..a6a59047a 100644
--- a/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs
+++ b/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs
@@ -63,10 +63,10 @@
target-container-id (or target-container-id (:parent-id shape))]
(filter some?
- [(when target-page-id (dw/initialize-page target-page-id))
+ [(when target-page-id (dw/initialize-page (:id file) target-page-id))
(dws/select-shape target-container-id)
(dw/paste-shapes pdata)
- (when target-page-id (dw/initialize-page (:id page)))])))
+ (when target-page-id (dw/initialize-page (:id file) (:id page)))])))
(defn- sync-file [file]
(map (fn [component-tag]
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index 025797e5d..46b567622 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -12,9 +12,8 @@ msgstr ""
"X-Generator: Weblate 5.10-dev\n"
#: src/app/main/data/common.cljs:87
-#, fuzzy
-msgid "Refresh"
-msgstr ""
+msgid "labels.refresh"
+msgstr "Refresh"
#: src/app/main/ui/auth/register.cljs:133, src/app/main/ui/static.cljs:155, src/app/main/ui/viewer/login.cljs:98
msgid "auth.already-have-account"
@@ -1386,11 +1385,6 @@ msgstr "Email or password is incorrect."
msgid "errors.wrong-old-password"
msgstr "Old password is incorrect"
-#: src/app/main/ui/workspace/sidebar/options/menus/bool.cljs:85
-#, fuzzy
-msgid "exclude"
-msgstr ""
-
#: src/app/main/ui/settings/feedback.cljs:74
msgid "feedback.description"
msgstr "Description"
@@ -1691,11 +1685,6 @@ msgstr "Text"
msgid "inspect.tabs.info"
msgstr "Info"
-#: src/app/main/ui/workspace/sidebar/options/menus/bool.cljs:80
-#, fuzzy
-msgid "intersection"
-msgstr ""
-
#: src/app/main/ui/workspace/main_menu.cljs:162
msgid "label.shortcuts"
msgstr "Shortcuts"
@@ -2020,6 +2009,10 @@ msgstr "Member"
msgid "labels.members"
msgstr "Members"
+#: src/app/main/ui/comments.cljs:558
+msgid "labels.mention"
+msgstr "Mention"
+
#: src/app/main/ui/settings/password.cljs:84
msgid "labels.new-password"
msgstr "New password"
@@ -2458,11 +2451,6 @@ msgstr "Create token"
msgid "modals.create-access-token.title"
msgstr "Generate access token"
-#: src/app/main/ui/settings/access_tokens.cljs:152
-#, fuzzy
-msgid "modals.create-access-token.token"
-msgstr ""
-
#: src/app/main/ui/dashboard/team.cljs:921
msgid "modals.create-webhook.submit-label"
msgstr "Create webhook"
@@ -4806,10 +4794,6 @@ msgstr "Loading…"
msgid "workspace.libraries.more-templates"
msgstr "You can look for "
-#: src/app/main/ui/workspace/libraries.cljs:391
-msgid "workspace.libraries.more-templates-link"
-msgstr "more templates in here"
-
#: src/app/main/ui/workspace/libraries.cljs:481
msgid "workspace.libraries.no-libraries-need-sync"
msgstr "There are no Shared Libraries that need update"
@@ -4818,10 +4802,6 @@ msgstr "There are no Shared Libraries that need update"
msgid "workspace.libraries.no-matches-for"
msgstr "No matches found for “%s“"
-#: src/app/main/ui/workspace/libraries.cljs:384
-msgid "workspace.libraries.no-shared-libraries-available"
-msgstr "There are no Shared Libraries available"
-
#: src/app/main/ui/workspace/libraries.cljs:337
msgid "workspace.libraries.search-shared-libraries"
msgstr "Search shared libraries"
@@ -6625,11 +6605,6 @@ msgstr "Select set."
msgid "workspace.token.set-selection-theme"
msgstr "Define what token sets should be used as part of this theme option:"
-#: src/app/main/ui/workspace/tokens/modals/themes.cljs:123
-#, fuzzy
-msgid "workspace.token.theme"
-msgstr ""
-
#: src/app/main/ui/workspace/tokens/modals/themes.cljs
#, unused
msgid "workspace.token.theme-name"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index d7d6c1f0c..14e900594 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -2015,6 +2015,10 @@ msgstr "Integrante"
msgid "labels.members"
msgstr "Integrantes"
+#: src/app/main/ui/comments.cljs:558
+msgid "labels.mention"
+msgstr "Mencionar"
+
#: src/app/main/ui/settings/password.cljs:84
msgid "labels.new-password"
msgstr "Nueva contraseña"
@@ -4800,10 +4804,6 @@ msgstr "Cargando…"
msgid "workspace.libraries.more-templates"
msgstr "Puedes buscar "
-#: src/app/main/ui/workspace/libraries.cljs:391
-msgid "workspace.libraries.more-templates-link"
-msgstr "más plantillas aquí"
-
#: src/app/main/ui/workspace/libraries.cljs:481
msgid "workspace.libraries.no-libraries-need-sync"
msgstr "No hay bibliotecas que necesiten ser actualizadas"
@@ -4812,10 +4812,6 @@ msgstr "No hay bibliotecas que necesiten ser actualizadas"
msgid "workspace.libraries.no-matches-for"
msgstr "No se encuentra “%s“"
-#: src/app/main/ui/workspace/libraries.cljs:384
-msgid "workspace.libraries.no-shared-libraries-available"
-msgstr "No hay bibliotecas compartidas disponibles"
-
#: src/app/main/ui/workspace/libraries.cljs:337
msgid "workspace.libraries.search-shared-libraries"
msgstr "Buscar bibliotecas compartidas"