Files
penpot/frontend/src/app/main/data/workspace/path/changes.cljs
Andrey Antukh d051a3ba45 🐛 Ensure path content is always PathData when saving
The save-path-content function only converted content to PathData when
there was a trailing :move-to command. When there was no trailing
:move-to, the content from get-path was stored as-is, which could be
a plain vector if the shape was already a :path type with non-PathData
content. This caused segment/get-points to fail with 'can't access
property "get", cache is undefined' when the with-cache macro tried
to access the cache field on a non-PathData object.

The fix ensures content is always converted to PathData via path/content
before being stored in the state.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-03-24 08:15:58 +01:00

92 lines
3.0 KiB
Clojure

;; 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.workspace.path.changes
(:require
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.types.path :as path]
[app.main.data.changes :as dch]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.path.state :as st]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(defn generate-path-changes
"Generates changes to update the new content of the shape"
[it objects page-id shape 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
objects
(update objects shape-id
(fn [shape]
(-> shape
(assoc :content old-content)
(path/update-geometry))))
changes
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects))
new-content
(path/content new-content)]
(cond
;; https://tree.taiga.io/project/penpot/issue/2366
(nil? shape-id)
changes
(empty? new-content)
(-> changes
(pcb/remove-objects [shape-id])
(pcb/resize-parents [shape-id]))
:else
(-> changes
(pcb/update-shapes [shape-id]
(fn [shape]
(-> shape
(assoc :content new-content)
(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))
(path/content (take (dec (count content)) content))
(path/content content))]
(st/set-content state content)))
ptk/WatchEvent
(watch [it state _]
(let [page-id (:current-page-id state)
local (get state :workspace-local)
id (get local :edition)
objects (dsh/lookup-page-objects state page-id)]
;; NOTE: we proceed only if the shape is present on the
;; objects, if shape is a ephimeral drawing shape, we should
;; do nothing
(when-let [shape (get objects id)]
(when-let [old-content (dm/get-in local [:edit-path id :old-content])]
(let [new-content (get shape :content)
changes (generate-path-changes it objects page-id shape old-content new-content)]
(rx/of (dch/commit-changes changes))))))))))