0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-12 18:18:24 -05:00

Merge remote-tracking branch 'origin/main' into develop

This commit is contained in:
alonso.torres 2022-06-06 15:23:22 +02:00
commit 9eba666c31
15 changed files with 317 additions and 207 deletions

View file

@ -19,6 +19,17 @@
### :arrow_up: Deps updates
### :heart: Community contributions by (Thank you!)
## 1.13.4-beta
### :bug: Bugs fixed
- Fix undo when drawing curves [Taiga #3523](https://tree.taiga.io/project/penpot/issue/3523)
- Fix issue with text edition and certain fonts (WorkSans, Raleway, ...) and foreign objects [Taiga #3521](https://tree.taiga.io/project/penpot/issue/3521)
- Fix thumbnail generation when concurrent edition [Taiga #3522](https://tree.taiga.io/project/penpot/issue/3522)
- Fix environment imporot for exporter in Docker
- Fix auto scroll layers in Firefox [Taiga #3531](https://tree.taiga.io/project/penpot/issue/3531)
- Fix base background not visible for imported SVG
## 1.13.3-beta
### :bug: Bugs fixed

View file

@ -23,7 +23,41 @@
[expound.alpha :as expound]
[fipp.edn :refer [pprint]]))
;; ==== Utility functions
(defn reset-file-data
"Hardcode replace of the data of one file."
[system id data]
(db/with-atomic [conn (:app.db/pool system)]
(db/update! conn :file
{:data data}
{:id id})))
(defn get-file
"Get the migrated data of one file."
[system id]
(-> (:app.db/pool system)
(db/get-by-id :file id)
(update :data app.util.blob/decode)
(update :data pmg/migrate-data)))
(defn duplicate-file
"This is a raw version of duplication of file just only for forensic analysis."
[system file-id email]
(db/with-atomic [conn (:app.db/pool system)]
(when-let [profile (some->> (prof/retrieve-profile-data-by-email conn (str/lower email))
(prof/populate-additional-data conn))]
(when-let [file (db/exec-one! conn (sql/select :file {:id file-id}))]
(let [params (assoc file
:id (uuid/next)
:project-id (:default-project-id profile))]
(db/insert! conn :file params)
(:id file))))))
(defn update-file
"Apply a function to the data of one file. Optionally save the changes or not.
The function receives the decoded and migrated file data."
([system id f] (update-file system id f false))
([system id f save?]
(db/with-atomic [conn (:app.db/pool system)]
@ -40,85 +74,115 @@
{:id (:id file)}))
(update file :data blob/decode)))))
(defn reset-file-data
[system id data]
(db/with-atomic [conn (:app.db/pool system)]
(db/update! conn :file
{:data data}
{:id id})))
(defn analyze-files
"Apply a function to all files in the database, reading them in batches. Do not change data.
The function receives an object with some properties of the file and the decoded data, and
an empty atom where it may accumulate statistics, if desired."
[system {:keys [sleep chunk-size max-chunks on-file]
:or {sleep 1000 chunk-size 10 max-chunks ##Inf}}]
(let [stats (atom {})]
(letfn [(retrieve-chunk [conn cursor]
(let [sql (str "select id, name, modified_at, data from file "
" where modified_at < ? and deleted_at is null "
" order by modified_at desc limit ?")]
(->> (db/exec! conn [sql cursor chunk-size])
(map #(update % :data blob/decode)))))
(defn get-file
[system id]
(-> (:app.db/pool system)
(db/get-by-id :file id)
(update :data app.util.blob/decode)
(update :data pmg/migrate-data)))
(process-chunk [chunk]
(loop [files chunk]
(when-let [file (first files)]
(on-file file stats)
(recur (rest files)))))]
(defn duplicate-file
"This is a raw version of duplication of file just only for forensic analysis"
[system file-id email]
(db/with-atomic [conn (:app.db/pool system)]
(when-let [profile (some->> (prof/retrieve-profile-data-by-email conn (str/lower email))
(prof/populate-additional-data conn))]
(when-let [file (db/exec-one! conn (sql/select :file {:id file-id}))]
(let [params (assoc file
:id (uuid/next)
:project-id (:default-project-id profile))]
(db/insert! conn :file params)
(:id file))))))
(db/with-atomic [conn (:app.db/pool system)]
(loop [cursor (dt/now)
chunks 0]
(when (< chunks max-chunks)
(let [chunk (retrieve-chunk conn cursor)]
(when-not (empty? chunk)
(let [cursor (-> chunk last :modified-at)]
(process-chunk chunk)
(Thread/sleep (inst-ms (dt/duration sleep)))
(recur cursor (inc chunks)))))))
@stats))))
(defn repair-orphaned-components
"We have detected some cases of component instances that are not nested, but
however they have not the :component-root? attribute (so the system considers
them nested). This script fixes this adding them the attribute.
(defn update-pages
"Apply a function to all pages of one file. The function receives a page and returns an updated page."
[data f]
(update data :pages-index d/update-vals f))
Use it with the update-file function above."
[data]
(let [update-page
(fn [page]
(prn "================= Page:" (:name page))
(letfn [(is-nested? [object]
(and (some? (:component-id object))
(nil? (:component-root? object))))
(defn update-shapes
"Apply a function to all shapes of one page The function receives a shape and returns an updated shape"
[page f]
(update page :objects d/update-vals f))
(is-instance? [object]
(some? (:shape-ref object)))
(get-parent [object]
(get (:objects page) (:parent-id object)))
;; ==== Specific fixes
(update-object [object]
(if (and (is-nested? object)
(not (is-instance? (get-parent object))))
(do
(prn "Orphan:" (:name object))
(assoc object :component-root? true))
object))]
(defn repair-orphaned-shapes
"There are some shapes whose parent has been deleted. This
function detects them and puts them as children of the root node."
([file _] ; to be called from analyze-files to search for files with the problem
(repair-orphaned-shapes (:data file)))
(update page :objects d/update-vals update-object)))]
([data]
(let [is-orphan? (fn [shape objects]
(and (some? (:parent-id shape))
(nil? (get objects (:parent-id shape)))))
(update data :pages-index d/update-vals update-page)))
update-page (fn [page]
(let [objects (:objects page)
orphans (set (filter #(is-orphan? % objects) (vals objects)))]
(if (seq orphans)
(do
(prn (:id data) "file has" (count orphans) "broken shapes")
(-> page
(update-shapes (fn [shape]
(if (orphans shape)
(assoc shape :parent-id uuid/zero)
shape)))
(update-in [:objects uuid/zero :shapes]
(fn [shapes] (into shapes (map :id orphans))))))
page)))]
(defn repair-idless-components
"There are some files that contains components with no :id attribute.
This function detects them and repairs it.
(update-pages data update-page))))
Use it with the update-file function above."
[data]
(letfn [(update-component [id component]
(if (nil? (:id component))
(do
(prn (:id data) "Broken component" (:name component) id)
(assoc component :id id))
component))]
(update data :components #(d/mapm update-component %))))
;; DO NOT DELETE already used scripts, could be taken as templates for easyly writing new ones
;; -------------------------------------------------------------------------------------------
(defn analyze-idless-components
"Scan all files to check if there are any one with idless components.
(Does not save the changes, only used to detect affected files)."
[file _]
(repair-idless-components (:data file)))
;; (defn repair-orphaned-components
;; "We have detected some cases of component instances that are not nested, but
;; however they have not the :component-root? attribute (so the system considers
;; them nested). This script fixes this adding them the attribute.
;;
;; Use it with the update-file function above."
;; [data]
;; (let [update-page
;; (fn [page]
;; (prn "================= Page:" (:name page))
;; (letfn [(is-nested? [object]
;; (and (some? (:component-id object))
;; (nil? (:component-root? object))))
;;
;; (is-instance? [object]
;; (some? (:shape-ref object)))
;;
;; (get-parent [object]
;; (get (:objects page) (:parent-id object)))
;;
;; (update-object [object]
;; (if (and (is-nested? object)
;; (not (is-instance? (get-parent object))))
;; (do
;; (prn "Orphan:" (:name object))
;; (assoc object :component-root? true))
;; object))]
;;
;; (update page :objects d/update-vals update-object)))]
;;
;; (update data :pages-index d/update-vals update-page)))
;; (defn check-image-shapes
;; [{:keys [data] :as file} stats]
@ -138,32 +202,3 @@
;; (when @affected?
;; (swap! stats update :affected-files (fnil inc 0)))))
(defn analyze-files
[system {:keys [sleep chunk-size max-chunks on-file]
:or {sleep 1000 chunk-size 10 max-chunks ##Inf}}]
(let [stats (atom {})]
(letfn [(retrieve-chunk [conn cursor]
(let [sql (str "select id, name, modified_at, data from file "
" where modified_at < ? and deleted_at is null "
" order by modified_at desc limit ?")]
(->> (db/exec! conn [sql cursor chunk-size])
(map #(update % :data blob/decode)))))
(process-chunk [chunk]
(loop [items chunk]
(when-let [item (first items)]
(on-file item stats)
(recur (rest items)))))]
(db/with-atomic [conn (:app.db/pool system)]
(loop [cursor (dt/now)
chunks 0]
(when (< chunks max-chunks)
(let [chunk (retrieve-chunk conn cursor)]
(when-not (empty? chunk)
(let [cursor (-> chunk last :modified-at)]
(process-chunk chunk)
(Thread/sleep (inst-ms (dt/duration sleep)))
(recur cursor (inc chunks)))))))
@stats))))

View file

@ -44,6 +44,8 @@ services:
penpot-exporter:
image: "penpotapp/exporter:latest"
env_file:
- config.env
environment:
# Don't touch it; this uses internal docker network to
# communicate with the frontend.

View file

@ -26,7 +26,7 @@
:http-server-port 6061
:http-server-host "localhost"
:redis-uri "redis://redis/0"
:exporter-domain-whitelist #{"localhost:3449"}})
:domain-white-list #{"localhost:3449"}})
(s/def ::http-server-port ::us/integer)
(s/def ::http-server-host ::us/string)
@ -45,7 +45,7 @@
::http-server-host
::browser-pool-max
::browser-pool-min
::domain-whitelist]))
::domain-white-list]))
(defn- read-env
[prefix]

View file

@ -3,8 +3,9 @@
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
var parent = this.parentNode,
parentComputedStyle = window.getComputedStyle(parent, null),
var parent = this.parentNode;
if (parent) {
var parentComputedStyle = window.getComputedStyle(parent, null),
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
@ -13,14 +14,15 @@
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
alignWithTop = overTop && !overBottom;
if ((overTop || overBottom) && centerIfNeeded) {
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
}
if ((overLeft || overRight) && centerIfNeeded) {
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
}
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
this.scrollIntoView(alignWithTop);
if ((overTop || overBottom) && centerIfNeeded) {
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
}
if ((overLeft || overRight) && centerIfNeeded) {
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
}
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
this.scrollIntoView(alignWithTop);
}
}
};
}

View file

@ -124,7 +124,7 @@
(let [edition (get-in state [:workspace-local :edition])
drawing (get state :workspace-drawing)]
;; Editors handle their own undo's
(when-not (or (some? edition) (and (not-empty drawing) (nil? (:object drawing))))
(when (and (nil? edition) (nil? (:object drawing)))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))]
@ -420,19 +420,26 @@
(reverse)
(into (d/ordered-set)))
empty-parents-xform
(comp
(map (fn [id] (get objects id)))
(map (fn [{:keys [shapes type] :as obj}]
(when (and (= :group type)
(zero? (count (remove #(contains? ids %) shapes))))
obj)))
(take-while some?)
(map :id))
find-all-empty-parents (fn recursive-find-empty-parents [empty-parents]
(let [all-ids (into empty-parents ids)
empty-parents-xform
(comp
(map (fn [id] (get objects id)))
(map (fn [{:keys [shapes type] :as obj}]
(when (and (= :group type)
(zero? (count (remove #(contains? all-ids %) shapes))))
obj)))
(take-while some?)
(map :id))
calculated-empty-parents (into #{} empty-parents-xform all-parents)]
(if (= empty-parents calculated-empty-parents)
empty-parents
(recursive-find-empty-parents calculated-empty-parents))))
empty-parents
;; Any parent whose children are all deleted, must be deleted too.
(into (d/ordered-set) empty-parents-xform all-parents)
(into (d/ordered-set) (find-all-empty-parents #{}))
changes (-> (pcb/empty-changes it page-id)
(pcb/with-page page)
@ -448,13 +455,13 @@
(pcb/update-shapes (map :id interacting-shapes)
(fn [shape]
(update shape :interactions
(fn [interactions]
(when interactions
(d/removev #(and (csi/has-destination %)
(contains? ids (:destination %)))
interactions))))))
(fn [interactions]
(when interactions
(d/removev #(and (csi/has-destination %)
(contains? ids (:destination %)))
interactions))))))
(cond->
(seq starting-flows)
(seq starting-flows)
(pcb/update-page-option :flows (fn [flows]
(reduce #(csp/remove-flow %1 (:id %2))
flows

View file

@ -189,10 +189,14 @@
(s/def ::file-change-event
(s/keys :req-un [::type ::profile-id ::file-id ::session-id ::revn ::changes]))
(defn handle-file-change
[{:keys [file-id changes] :as msg}]
(us/assert ::file-change-event msg)
(ptk/reify ::handle-file-change
IDeref
(-deref [_] {:changes changes})
ptk/WatchEvent
(watch [_ _ _]
(let [position-data-operation?

View file

@ -18,6 +18,10 @@
([state page-id]
(get-in state [:workspace-data :pages-index page-id])))
(defn lookup-data-objects
[data page-id]
(dm/get-in data [:pages-index page-id :objects]))
(defn lookup-page-objects
([state]
(lookup-page-objects state (:current-page-id state)))

View file

@ -355,7 +355,7 @@
(assoc :svg-attrs (dissoc attrs :x :y :width :height :href :xlink:href))))))
(defn parse-svg-element [frame-id svg-data element-data unames]
(let [{:keys [tag attrs]} element-data
(let [{:keys [tag attrs hidden]} element-data
attrs (usvg/format-styles attrs)
element-data (cond-> element-data (map? element-data) (assoc :attrs attrs))
name (dwc/generate-unique-name unames (or (:id attrs) (tag->name tag)))
@ -402,6 +402,9 @@
(setup-fill)
(setup-stroke))
shape (cond-> shape
hidden (assoc :hidden true))
children (cond->> (:content element-data)
(or (= tag :g) (= tag :svg))
(mapv #(usvg/inherit-attributes attrs %)))]
@ -471,6 +474,7 @@
:height (str (:height root-shape))
:fill "none"
:id "base-background"}
:hidden true
:content []}
svg-data (-> svg-data

View file

@ -10,6 +10,7 @@
[app.common.pages.helpers :as cph]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.refs :as refs]
[app.main.repo :as rp]
[app.main.store :as st]
@ -31,7 +32,9 @@
[object-id]
(rx/create
(fn [subs]
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'" object-id))]
;; We look in the DOM a canvas that 1) matches the id and 2) that it's not empty
;; will be empty on first rendering before drawing the thumbnail and we don't want to store that
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%']:not([data-empty])" object-id))]
(if (some? node)
(-> node
(.toBlob (fn [blob]
@ -43,6 +46,14 @@
(do (rx/push! subs nil)
(rx/end! subs)))))))
(defn clear-thumbnail
[page-id frame-id]
(ptk/reify ::clear-thumbnail
ptk/UpdateEvent
(update [_ state]
(let [object-id (dm/str page-id frame-id)]
(assoc-in state [:workspace-file :thumbnails object-id] nil)))))
(defn update-thumbnail
"Updates the thumbnail information for the given frame `id`"
[page-id frame-id]
@ -71,50 +82,39 @@
(defn- extract-frame-changes
"Process a changes set in a commit to extract the frames that are changing"
[[event [old-objects new-objects]]]
[[event [old-data new-data]]]
(let [changes (-> event deref :changes)
extract-ids
(fn [{type :type :as change}]
(fn [{:keys [page-id type] :as change}]
(case type
:add-obj [(:id change)]
:mod-obj [(:id change)]
:del-obj [(:id change)]
:reg-objects (:shapes change)
:mov-objects (:shapes change)
:add-obj [[page-id (:id change)]]
:mod-obj [[page-id (:id change)]]
:del-obj [[page-id (:id change)]]
:mov-objects (->> (:shapes change) (map #(vector page-id %)))
[]))
get-frame-id
(fn [id]
(let [shape (or (get new-objects id)
(get old-objects id))]
(or (and (cph/frame-shape? shape) id) (:frame-id shape))))
(fn [[page-id id]]
(let [old-objects (wsh/lookup-data-objects old-data page-id)
new-objects (wsh/lookup-data-objects new-data page-id)
;; Extracts the frames and then removes nils and the root frame
xform (comp (mapcat extract-ids)
(map get-frame-id)
(remove nil?)
(filter #(not= uuid/zero %))
(filter #(contains? new-objects %)))]
new-shape (get new-objects id)
old-shape (get old-objects id)
(into #{} xform changes)))
old-frame-id (if (cph/frame-shape? old-shape) id (:frame-id old-shape))
new-frame-id (if (cph/frame-shape? new-shape) id (:frame-id new-shape))]
(defn thumbnail-change?
"Checks if a event is only updating thumbnails to ignore in the thumbnail generation process"
[event]
(let [changes (-> event deref :changes)
(cond-> #{}
(and old-frame-id (not= uuid/zero old-frame-id))
(conj [page-id old-frame-id])
is-thumbnail-op?
(fn [{type :type attr :attr}]
(and (= type :set)
(= attr :thumbnail)))
is-thumbnail-change?
(fn [change]
(and (= (:type change) :mod-obj)
(->> change :operations (every? is-thumbnail-op?))))]
(->> changes (every? is-thumbnail-change?))))
(and new-frame-id (not= uuid/zero new-frame-id))
(conj [page-id new-frame-id]))))]
(into #{}
(comp (mapcat extract-ids)
(mapcat get-frame-id))
changes)))
(defn watch-state-changes
"Watch the state for changes inside frames. If a change is detected will force a rendering
@ -123,32 +123,39 @@
(ptk/reify ::watch-state-changes
ptk/WatchEvent
(watch [_ _ stream]
(let [stopper (->> stream
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
(= ::watch-state-changes (ptk/type %)))))
(let [stopper
(->> stream
(rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %))
(= ::watch-state-changes (ptk/type %)))))
objects-stream (->> (rx/concat
(rx/of nil)
(rx/from-atom refs/workspace-page-objects {:emit-current-value? true}))
;; We need to keep the old-objects so we can check the frame for the
;; deleted objects
(rx/buffer 2 1))
workspace-data-str
(->> (rx/concat
(rx/of nil)
(rx/from-atom refs/workspace-data {:emit-current-value? true}))
;; We need to keep the old-objects so we can check the frame for the
;; deleted objects
(rx/buffer 2 1))
frame-changes (->> stream
(rx/filter dch/commit-changes?)
change-str
(->> stream
(rx/filter #(or (dch/commit-changes? %)
(= (ptk/type %) :app.main.data.workspace.notifications/handle-file-change)))
(rx/observe-on :async))
;; Async so we wait for additional side-effects of commit-changes
(rx/observe-on :async)
(rx/filter (complement thumbnail-change?))
(rx/with-latest-from objects-stream)
(rx/map extract-frame-changes)
(rx/share))]
frame-changes-str
(->> change-str
(rx/with-latest-from workspace-data-str)
(rx/flat-map extract-frame-changes)
(rx/share))]
(->> frame-changes
(rx/flat-map
(fn [ids]
(->> (rx/from ids)
(rx/map #(ptk/data-event ::force-render %)))))
(->> (rx/merge
(->> frame-changes-str
(rx/filter (fn [[page-id _]] (not= page-id (:current-page-id @st/state))))
(rx/map (fn [[page-id frame-id]] (clear-thumbnail page-id frame-id))))
(->> frame-changes-str
(rx/filter (fn [[page-id _]] (= page-id (:current-page-id @st/state))))
(rx/map (fn [[_ frame-id]] (ptk/data-event ::force-render frame-id)))))
(rx/take-until stopper))))))
(defn duplicate-thumbnail

View file

@ -324,7 +324,8 @@
props (obj/merge! #js {} props
#js {:childs childs
:objects objects})]
[:> group-wrapper props]))))
(when (not-empty childs)
[:> group-wrapper props])))))
(defn bool-container-factory
[objects]

View file

@ -82,6 +82,7 @@
frame? (= :frame type)
group? (= :group type)
text? (= :text type)
mask? (and group? masked-group?)]
(cond
@ -103,6 +104,10 @@
(dom/query-all shape-defs ".svg-def")
(dom/query-all shape-defs ".svg-mask-wrapper")))
text?
[shape-node
(dom/query shape-node ".text-container")]
:else
[shape-node])))
@ -185,6 +190,15 @@
(dom/class? node "frame-children")
(set-transform-att! node "transform" (gmt/inverse transform))
(dom/class? node "text-container")
(let [modifiers (dissoc modifiers :displacement :rotation)]
(when (not (gsh/empty-modifiers? modifiers))
(let [mtx (-> shape
(assoc :modifiers modifiers)
(gsh/transform-shape)
(gsh/transform-matrix {:no-flip true}))]
(override-transform-att! node "transform" mtx))))
(or (= (dom/get-tag-name node) "mask")
(= (dom/get-tag-name node) "filter"))
(transform-region! node modifiers)

View file

@ -32,6 +32,7 @@
(.clearRect canvas-context 0 0 canvas-width canvas-height)
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
(.removeAttribute canvas-node "data-empty")
true))
(catch :default err
(.error js/console err)
@ -75,6 +76,8 @@
thumbnail-data-ref (mf/use-memo (mf/deps page-id id) #(refs/thumbnail-frame-data page-id id))
thumbnail-data (mf/deref thumbnail-data-ref)
prev-thumbnail-data (hooks/use-previous thumbnail-data)
render-frame? (mf/use-state (not thumbnail-data))
on-image-load
@ -141,6 +144,12 @@
(.observe observer node #js {:childList true :attributes true :attributeOldValue true :characterData true :subtree true})
(reset! observer-ref observer)))))]
(mf/use-effect
(mf/deps thumbnail-data)
(fn []
(when (and (some? prev-thumbnail-data) (nil? thumbnail-data))
(rx/push! updates-str :update))))
(mf/use-effect
(mf/deps @render-frame? thumbnail-data)
(fn []
@ -198,8 +207,10 @@
[:foreignObject {:x x :y y :width width :height height}
[:canvas.thumbnail-canvas
{:ref frame-canvas-ref
{:key (dm/str "thumbnail-canvas-" (:id shape))
:ref frame-canvas-ref
:data-object-id (dm/str page-id (:id shape))
:data-empty true
:width fixed-width
:height fixed-height
;; DEBUG

View file

@ -7,9 +7,10 @@
(ns app.main.ui.workspace.shapes.text.editor
(:require
["draft-js" :as draft]
[app.common.geom.matrix :as gmt]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.text :as gsht]
[app.common.text :as txt]
[app.main.data.workspace :as dw]
[app.main.data.workspace.texts :as dwt]
@ -255,30 +256,37 @@
(-> (gpt/subtract pt box)
(gpt/multiply zoom)))))
(mf/defc text-editor-viewport
(mf/defc text-editor-svg
{::mf/wrap-props false}
[props]
(let [shape (obj/get props "shape")
viewport-ref (obj/get props "viewport-ref")
zoom (obj/get props "zoom")
position
(-> (gpt/point (-> shape :selrect :x)
(-> shape :selrect :y))
(translate-point-from-viewport (mf/ref-val viewport-ref) zoom))
clip-id
(dm/str "text-edition-clip" (:id shape))
top-left-corner (gpt/point (/ (:width shape) 2) (/ (:height shape) 2))
text-modifier-ref
(mf/use-memo (mf/deps (:id shape)) #(refs/workspace-text-modifier-by-id (:id shape)))
transform
(-> (gmt/matrix)
(gmt/scale (gpt/point zoom))
(gmt/multiply (gsh/transform-matrix shape nil top-left-corner)))]
text-modifier
(mf/deref text-modifier-ref)
[:div {:style {:position "absolute"
:left (str (:x position) "px")
:top (str (:y position) "px")
:pointer-events "all"
:transform (str transform)
:transform-origin "left top"}}
bounding-box
(gsht/position-data-bounding-box text-modifier)]
[:& text-shape-edit-html {:shape shape :key (str (:id shape))}]]))
[:g.text-editor {:clip-path (dm/fmt "url(#%)" clip-id)
:transform (dm/str (gsh/transform-matrix shape))}
[:defs
[:clipPath {:id clip-id}
[:rect {:x (min (:x bounding-box) (:x shape))
:y (min (:y bounding-box) (:y shape))
:width (max (:width bounding-box) (:width shape))
:height (max (:height bounding-box) (:height shape))
:fill "red"}]]]
[:foreignObject {:x (:x shape) :y (:y shape) :width "100%" :height "100%"
:externalResourcesRequired true}
[:div {:style {:position "absolute"
:left 0
:top 0
:pointer-events "all"}}
[:& text-shape-edit-html {:shape shape :key (str (:id shape))}]]]]))

View file

@ -187,10 +187,7 @@
[:div.viewport
[:div.viewport-overlays {:ref overlays-ref}
(when show-text-editor?
[:& editor/text-editor-viewport {:shape editing-shape
:viewport-ref viewport-ref
:zoom zoom}])
(when show-comments?
[:& comments/comments-layer {:vbox vbox
:vport vport
@ -275,6 +272,9 @@
:on-pointer-up on-pointer-up}
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
(when show-text-editor?
[:& editor/text-editor-svg {:shape editing-shape}])
(when show-outlines?
[:& outline/shape-outlines
{:objects base-objects