Compare commits

...

5 Commits

Author SHA1 Message Date
Andrey Antukh
16090f28df WIP 2026-01-19 19:45:36 +01:00
Andrey Antukh
afb10172ca WIP 2026-01-19 18:32:19 +01:00
Andrey Antukh
3ecf509f3b 🔧 Add missing gif files handling on defaut nginx config 2026-01-19 13:32:01 +01:00
Dalai Felinto
7f395b2642 🐛 Fix import tokens dialog default option (#8051)
This was intended to be changed on 13fcf3a9bb. However only the menu
order changed, not the default option.

Signed-off-by: Dalai Felinto <dalai@blender.org>
Co-authored-by: Dalai Felinto <dalai@blender.org>
2026-01-19 11:14:34 +01:00
Juan de la Cruz
2a62bd2586 🎉 Add 2.13 release slides (#8116) 2026-01-19 11:09:58 +01:00
16 changed files with 362 additions and 97 deletions

View File

@@ -25,7 +25,8 @@
(defn append
[{index :index items :items :as stack} value]
(if (and (some? stack) (not= value (peek stack)))
(if (and (some? stack)
(not= value (peek stack)))
(let [items (cond-> items
(> index 0)
(subvec 0 (inc index))
@@ -35,7 +36,6 @@
:always
(conj value))
index (min (dec MAX-UNDO-SIZE) (inc index))]
{:index index
:items items})

View File

@@ -223,7 +223,7 @@ http {
add_header X-Cache-Status $upstream_cache_status;
}
location ~* \.(jpg|png|svg|ttf|woff|woff2)$ {
location ~* \.(jpg|png|svg|ttf|woff|woff2|gif)$ {
add_header Cache-Control "public, max-age=604800" always; # 7 days
}

View File

@@ -144,7 +144,7 @@ http {
location / {
include /etc/nginx/overrides/location.d/*.conf;
location ~* \.(js|css|jpg|png|svg|ttf|woff|woff2|wasm)$ {
location ~* \.(js|css|jpg|png|svg|gif|ttf|woff|woff2|wasm)$ {
add_header Cache-Control "public, max-age=604800" always; # 7 days
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

View File

@@ -15,6 +15,8 @@
(declare clear-edition-mode)
;; FIXME: rename to `enter-edition-mode`
(defn start-edition-mode
"Mark a shape in edition mode"
[id]
@@ -42,6 +44,8 @@
;; update namespace reference in the
;; app/main/data/workspace/path/undo.cljs file.
;; FIXME: rename to `exit-edition-mode`
(defn clear-edition-mode
[]
(ptk/reify ::clear-edition-mode

View File

@@ -17,14 +17,12 @@
(defn generate-path-changes
"Generates changes to update the new content of the shape"
[it objects page-id shape old-content new-content]
[it objects page-id shape-id old-content new-content]
(assert (path/content? old-content))
(assert (path/content? new-content))
(let [shape-id (:id shape)
;; We set the old values so the update-shapes works
(let [;; We set the old values so the update-shapes works
objects
(update objects shape-id
(fn [shape]
@@ -58,32 +56,32 @@
(path/update-geometry))))
(pcb/resize-parents [shape-id])))))
(defn save-path-content
([]
(save-path-content {}))
([{:keys [preserve-move-to] :or {preserve-move-to false}}]
(ptk/reify ::save-path-content
ptk/UpdateEvent
(update [_ state]
(let [content (st/get-path state :content)
content (if (and (not preserve-move-to)
(= (-> content last :command) :move-to))
(into [] (take (dec (count content)) content))
content)]
(-> state
(st/set-content content))))
;; (defn save-path-content
;; ([]
;; (save-path-content {}))
;; ([{:keys [preserve-move-to] :or {preserve-move-to false}}]
;; (ptk/reify ::save-path-content
;; ptk/UpdateEvent
;; (update [_ state]
;; (let [content (st/get-path state :content)
;; content (if (and (not preserve-move-to)
;; (= (-> content last :command) :move-to))
;; (into [] (take (dec (count content)) content))
;; content)]
;; (-> state
;; (st/set-content content))))
ptk/WatchEvent
(watch [it state _]
(let [page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
id (dm/get-in state [:workspace-local :edition])
old-content (dm/get-in state [:workspace-local :edit-path id :old-content])
shape (st/get-path state)]
;; ptk/WatchEvent
;; (watch [it state _]
;; (let [page-id (:current-page-id state)
;; objects (dsh/lookup-page-objects state page-id)
;; id (dm/get-in state [:workspace-local :edition])
;; old-content (dm/get-in state [:workspace-local :edit-path id :old-content])
;; shape (st/get-path state)]
(if (and (some? old-content) (some? (:id shape)))
(let [changes (generate-path-changes it objects page-id shape old-content (:content shape))]
(rx/of (dch/commit-changes changes)))
(rx/empty)))))))
;; (if (and (some? old-content) (some? (:id shape)))
;; (let [changes (generate-path-changes it objects page-id id old-content (:content shape))]
;; (rx/of (dch/commit-changes changes)))
;; (rx/empty)))))))

View File

@@ -17,6 +17,7 @@
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.main.data.changes :as dch]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.drawing.common :as dwdc]
[app.main.data.workspace.edition :as dwe]
@@ -120,13 +121,15 @@
(ptk/reify ::finish-drag
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
(let [id (st/get-path-id state)
modifiers (get-in state [:workspace-local :edit-path id :content-modifiers])
content (-> (st/get-path state :content)
(path/apply-content-modifiers modifiers))
content (-> (st/get-path state :content)
(path/apply-content-modifiers modifiers))
handler (get-in state [:workspace-local :edit-path id :drag-handler])]
handler (get-in state [:workspace-local :edit-path id :drag-handler])]
(prn "finish-drag")
(-> state
(st/set-content content)
(update-in [:workspace-local :edit-path id] dissoc :drag-handler)
@@ -225,7 +228,7 @@
(rx/of (finish-drag)))))))
(defn- start-edition
[_id]
[id]
(ptk/reify ::start-edition
ptk/UpdateEvent
(update [_ state]
@@ -351,6 +354,8 @@
(let [id (dm/get-in state [:workspace-local :edition])
objects (dsh/lookup-page-objects state)
content (dm/get-in objects [id :content])]
(prn "start-draw-mode" id)
(if content
(update-in state [:workspace-local :edit-path id] assoc :old-content content)
state)))
@@ -359,14 +364,15 @@
(watch [_ _ _]
(rx/of (start-draw-mode*)))))
(defn start-draw-mode*
[]
(defn- start-draw-mode*
[id]
(ptk/reify ::start-draw-mode*
ptk/WatchEvent
(watch [_ state stream]
(let [local (get state :workspace-local)
id (get local :edition)
mode (dm/get-in local [:edit-path id :edit-mode])]
(prn "start-draw-mode*" id)
(if (= :draw mode)
(rx/concat
@@ -376,7 +382,7 @@
(rx/filter (ptk/type? ::end-edition))
(rx/take 1)
(rx/mapcat (fn [_]
(rx/of (check-changed-content)
(rx/of (check-changed-content id)
(start-draw-mode*))))))
(rx/empty))))))
@@ -386,7 +392,7 @@
ptk/UpdateEvent
(update [_ state]
(if-let [id (dm/get-in state [:workspace-local :edition])]
(d/update-in-when state [:workspace-local :edit-path id] assoc :edit-mode mode)
(update-in state [:workspace-local :edit-path id] assoc :edit-mode mode)
state))
ptk/WatchEvent
@@ -407,21 +413,29 @@
(assoc-in state [:workspace-local :edit-path id :prev-handler] nil)))))
(defn check-changed-content
[]
[id]
(ptk/reify ::check-changed-content
ptk/WatchEvent
(watch [_ state _]
(let [id (st/get-path-id state)
content (st/get-path state :content)
old-content (get-in state [:workspace-local :edit-path id :old-content])
mode (get-in state [:workspace-local :edit-path id :edit-mode])
empty-content? (empty? content)]
(watch [it state _]
(let [
;; id (st/get-path-id state)
content (st/get-path state :content)
empty-content? (empty? content)
local (get-in state [:workspace-local :edit-path id])
old-content (get local :old-content)
edit-mode (get local :edit-mode)]
(prn "check-changed-content" old-content)
(prn "check-changed-content" content)
(cond
(and (not= content old-content) (not empty-content?))
(rx/of (changes/save-path-content))
(let [page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
changes (changes/generate-path-changes it objects page-id id old-content content)]
(rx/of (dch/commit-changes changes)))
(= mode :draw)
(= :draw edit-mode)
(rx/of :interrupt)
:else

View File

@@ -65,7 +65,7 @@
point-change (->> (map hash-map old-points new-points) (reduce merge))]
(when (and (some? new-content) (some? shape))
(let [changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
(let [changes (changes/generate-path-changes it objects page-id (:id shape) (:content shape) new-content)]
(if (empty? new-content)
(rx/of (dch/commit-changes changes)
(dwe/clear-edition-mode))
@@ -298,8 +298,8 @@
edit-path (dm/get-in state [:workspace-local :edit-path id])
content (st/get-path state :content)
state (cond-> state
(cfh/path-shape? objects id)
(st/set-content (path/close-subpaths content)))]
#_(cfh/path-shape? objects id)
#_(st/set-content (path/close-subpaths content)))]
(cond-> state
(or (not edit-path)
@@ -338,19 +338,25 @@
(defn split-segments
[{:keys [from-p to-p t]}]
(ptk/reify ::split-segments
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
content (st/get-path state :content)]
(-> state
(assoc-in [:workspace-local :edit-path id :old-content] content)
(st/set-content (-> content
(path.segment/split-segments #{from-p to-p} t)
(path/content))))))
ptk/WatchEvent
(watch [_ _ _]
(rx/of (changes/save-path-content {:preserve-move-to true})))))
(watch [it state _]
(let [page-id (get state :current-page-id)
objects (dsh/lookup-page-objects state page-id)
shape (st/get-path state)
shape-id (get shape :id)
old-content (get shape :content)
new-content (-> old-content
(path.segment/split-segments #{from-p to-p} t)
(path/content))
changes (changes/generate-path-changes it objects page-id shape-id old-content new-content)]
(prn "split-segments" old-content)
(prn "split-segments" new-content)
(rx/of (dch/commit-changes changes))))))
(defn create-node-at-position
[event]

View File

@@ -44,7 +44,7 @@
(path/close-subpaths))
changes
(changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
(changes/generate-path-changes it objects page-id (:id shape) (:content shape) new-content)]
(rx/concat
(rx/of (dwsh/update-shapes [id] path/convert-to-path)

View File

@@ -10,7 +10,9 @@
[app.common.data.undo-stack :as u]
[app.common.uuid :as uuid]
[app.main.data.workspace.common :as dwc]
[app.main.data.changes :as dch]
[app.main.data.workspace.edition :as-alias dwe]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.pages :as-alias dwpg]
[app.main.data.workspace.path.changes :as changes]
[app.main.data.workspace.path.common :as common]
@@ -57,44 +59,63 @@
(ptk/reify ::undo-path
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
undo-stack (-> (get-in state [:workspace-local :edit-path id :undo-stack])
(u/undo))
entry (u/peek undo-stack)]
(cond-> state
(some? entry)
(-> (load-entry entry)
(d/assoc-in-when
[:workspace-local :edit-path id :undo-stack]
undo-stack)))))
(let [id (st/get-path-id state)]
(update-in state [:workspace-local :edit-path id :undo-stack] u/undo)))
ptk/WatchEvent
(watch [_ state _]
(let [id (st/get-path-id state)
undo-stack (get-in state [:workspace-local :edit-path id :undo-stack])]
(if (> (:index undo-stack) 0)
(rx/of (changes/save-path-content {:preserve-move-to true}))
(rx/of (changes/save-path-content {:preserve-move-to true})
(common/finish-path)
(dwc/show-toolbar)))))))
(watch [it state _]
(let [id (st/get-path-id state)
shape (st/get-path state)
page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
edition? (= (get-in state [:workspace-local :edition]) id)
ustack (get-in state [:workspace-local :edit-path id :undo-stack])
entry (u/peek ustack)
old-content (get shape :content)
new-content (get entry :content)
changes (changes/generate-path-changes it objects page-id id old-content new-content)]
(rx/concat
(rx/of #(load-entry % entry))
(if edition?
(rx/of (dch/commit-changes changes))
(rx/empty))
(if (zero? (:index ustack))
(rx/of (common/finish-path)
(dwc/show-toolbar))
(rx/empty)))))))
(defn redo-path []
(ptk/reify ::redo-path
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
undo-stack (-> (get-in state [:workspace-local :edit-path id :undo-stack])
(u/redo))
entry (u/peek undo-stack)]
(let [id (st/get-path-id state)
ustack (-> (get-in state [:workspace-local :edit-path id :undo-stack])
(u/redo))
entry (u/peek ustack)]
(-> state
(load-entry entry)
(d/assoc-in-when
[:workspace-local :edit-path id :undo-stack]
undo-stack))))
(d/assoc-in-when [:workspace-local :edit-path id :undo-stack] ustack))))
ptk/WatchEvent
(watch [_ _ _]
(rx/of (changes/save-path-content)))))
(watch [it state _]
(let [id (st/get-path-id state)
shape (st/get-path state)
page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
ustack (get-in state [:workspace-local :edit-path id :undo-stack])
entry (u/peek ustack)
old-content (get shape :content)
new-content (get entry :content)
changes (changes/generate-path-changes it objects page-id id old-content new-content)]
(rx/of (dch/commit-changes changes))))))
(defn merge-head
"Joins the head with the previous undo in one. This is done so when the user changes a
@@ -149,6 +170,7 @@
(ptk/reify ::start-path-undo
ptk/UpdateEvent
(update [_ state]
(let [undo-lock (get-in state [:workspace-local :edit-path (st/get-path-id state) :undo-lock])]
(cond-> state
(not undo-lock)

View File

@@ -31,6 +31,7 @@
[app.main.ui.releases.v2-10]
[app.main.ui.releases.v2-11]
[app.main.ui.releases.v2-12]
[app.main.ui.releases.v2-13]
[app.main.ui.releases.v2-2]
[app.main.ui.releases.v2-3]
[app.main.ui.releases.v2-4]
@@ -103,4 +104,4 @@
(defmethod rc/render-release-notes "0.0"
[params]
(rc/render-release-notes (assoc params :version "2.12")))
(rc/render-release-notes (assoc params :version "2.13")))

View File

@@ -0,0 +1,118 @@
;; 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.ui.releases.v2-13
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.ui.releases.common :as c]
[rumext.v2 :as mf]))
(defmethod c/render-release-notes "2.13"
[{:keys [slide klass next finish navigate version]}]
(mf/html
(case slide
:start
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.13-slide-0.jpg"
:class (stl/css :start-image)
:border "0"
:alt "Penpot 2.13 is here!"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"Whats new in Penpot?"]
[:div {:class (stl/css :version-tag)}
(dm/str "Version " version)]]
[:div {:class (stl/css :features-block)}
[:span {:class (stl/css :feature-title)}
"The first release of the year, and were just getting started 🚀"]
[:p {:class (stl/css :feature-content)}
"This is our first release of the year, and it sets the tone for whats coming next. Were kicking off an exciting year where well take Penpot to a whole new level, with improved performance, stronger design system foundations, long-requested features, and new capabilities that unlock better workflows for teams."]
[:p {:class (stl/css :feature-content)}
"This release brings two highlights the community has been asking for, along with solid improvements under the hood to keep everything fast and smooth."]
[:p {:class (stl/css :feature-content)}
"Lets dive in!"]]
[:div {:class (stl/css :navigation)}
[:button {:class (stl/css :next-btn)
:on-click next} "Continue"]]]]]]
0
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.13-trash.gif"
:class (stl/css :start-image)
:border "0"
:alt "The Trash"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"The Trash"]]
[:div {:class (stl/css :feature)}
[:p {:class (stl/css :feature-content)}
"Deleting a file no longer means its gone forever. Introducing The Trash, a dedicated space in the dashboard where deleted files and projects live before being permanently removed."]
[:p {:class (stl/css :feature-content)}
"From here, you can recover content deleted by mistake or clean things up for good when youre sure you dont need them anymore. The Trash works for both files and projects, and items are automatically removed after a period of time depending on your Penpot plan."]
[:p {:class (stl/css :feature-content)}
"Highly requested, long overdue, and now officially here."]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 3}]
[:button {:on-click next
:class (stl/css :next-btn)} "Continue"]]]]]]
1
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.13-shadow-tokens.gif"
:class (stl/css :start-image)
:border "0"
:alt "Shadow tokens: Reusable shadows, at last!"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"Shadow tokens: Reusable shadows, at last!"]]
[:div {:class (stl/css :feature)}
[:p {:class (stl/css :feature-content)}
"With Shadow tokens, were introducing our second composite token, right after Typography tokens. This is a big step forward for design systems in Penpot."]
[:p {:class (stl/css :feature-content)}
"Until now, shadows couldnt be defined as reusable styles the way colors could before color tokens existed. Shadow tokens change that. You can now create reusable, consistent shadows, made of one or multiple layers, fully tokenized and ready to scale across your designs."]
[:p {:class (stl/css :feature-content)}
"Each shadow can reference existing tokens or use custom values, supports both Drop Shadow and Inner Shadow, and even allows shadow tokens to reference other shadow tokens. A brand-new capability, unlocked."]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 2}]
[:button {:on-click finish
:class (stl/css :next-btn)} "Let's go"]]]]]])))

View File

@@ -0,0 +1,102 @@
// 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
@use "refactor/common-refactor.scss" as deprecated;
.modal-overlay {
@extend .modal-overlay-base;
}
.modal-container {
display: grid;
grid-template-columns: deprecated.$s-324 1fr;
height: deprecated.$s-500;
width: deprecated.$s-888;
border-radius: deprecated.$br-8;
background-color: var(--modal-background-color);
border: deprecated.$s-2 solid var(--modal-border-color);
}
.start-image {
width: deprecated.$s-324;
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
}
.modal-content {
padding: deprecated.$s-40;
display: grid;
grid-template-rows: auto 1fr deprecated.$s-32;
gap: deprecated.$s-24;
a {
color: var(--button-primary-background-color-rest);
}
}
.modal-header {
display: grid;
gap: deprecated.$s-8;
}
.version-tag {
@include deprecated.flexCenter;
@include deprecated.headlineSmallTypography;
height: deprecated.$s-32;
width: deprecated.$s-96;
background-color: var(--communication-tag-background-color);
color: var(--communication-tag-foreground-color);
border-radius: deprecated.$br-8;
}
.modal-title {
@include deprecated.headlineLargeTypography;
color: var(--modal-title-foreground-color);
}
.features-block {
display: flex;
flex-direction: column;
gap: deprecated.$s-16;
width: deprecated.$s-440;
}
.feature {
display: flex;
flex-direction: column;
gap: deprecated.$s-8;
}
.feature-title {
@include deprecated.bodyLargeTypography;
color: var(--modal-title-foreground-color);
}
.feature-content {
@include deprecated.bodyMediumTypography;
margin: 0;
color: var(--modal-text-foreground-color);
}
.feature-list {
@include deprecated.bodyMediumTypography;
color: var(--modal-text-foreground-color);
list-style: disc;
display: grid;
gap: deprecated.$s-8;
}
.navigation {
width: 100%;
display: grid;
grid-template-areas: "bullets button";
}
.next-btn {
@extend .button-primary;
width: deprecated.$s-100;
justify-self: flex-end;
grid-area: button;
}

View File

@@ -273,4 +273,4 @@
{:label (tr "workspace.tokens.import-menu-folder-option") :value :folder}]
:on-click handle-import-action
:text-render render-button-text
:default :zip}]]]))
:default :file}]]]))