mirror of
https://github.com/penpot/penpot.git
synced 2026-02-12 14:42:56 +00:00
Merge branch 'staging-render' into develop
This commit is contained in:
@@ -34,6 +34,12 @@
|
|||||||
- Fix viewer can update library [Taiga #13186](https://tree.taiga.io/project/penpot/issue/13186)
|
- Fix viewer can update library [Taiga #13186](https://tree.taiga.io/project/penpot/issue/13186)
|
||||||
- Fix remove fill affects different element than selected [Taiga #13128](https://tree.taiga.io/project/penpot/issue/13128)
|
- Fix remove fill affects different element than selected [Taiga #13128](https://tree.taiga.io/project/penpot/issue/13128)
|
||||||
|
|
||||||
|
## 2.13.1
|
||||||
|
|
||||||
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
- Fix PDF Exporter outputs empty page when board has A4 format [Taiga #13181](https://tree.taiga.io/project/penpot/issue/13181)
|
||||||
|
|
||||||
## 2.13.0
|
## 2.13.0
|
||||||
|
|
||||||
### :heart: Community contributions (Thank you!)
|
### :heart: Community contributions (Thank you!)
|
||||||
|
|||||||
@@ -35,8 +35,7 @@
|
|||||||
javax.xml.parsers.SAXParserFactory
|
javax.xml.parsers.SAXParserFactory
|
||||||
org.apache.commons.io.IOUtils
|
org.apache.commons.io.IOUtils
|
||||||
org.im4java.core.ConvertCmd
|
org.im4java.core.ConvertCmd
|
||||||
org.im4java.core.IMOperation
|
org.im4java.core.IMOperation))
|
||||||
org.im4java.core.Info))
|
|
||||||
|
|
||||||
(def default-max-file-size
|
(def default-max-file-size
|
||||||
(* 1024 1024 10)) ; 10 MiB
|
(* 1024 1024 10)) ; 10 MiB
|
||||||
@@ -224,17 +223,18 @@
|
|||||||
;; If we are processing an animated gif we use the first frame with -scene 0
|
;; If we are processing an animated gif we use the first frame with -scene 0
|
||||||
(let [dim-result (sh/sh "identify" "-format" "%w %h\n" path)
|
(let [dim-result (sh/sh "identify" "-format" "%w %h\n" path)
|
||||||
orient-result (sh/sh "identify" "-format" "%[EXIF:Orientation]\n" path)]
|
orient-result (sh/sh "identify" "-format" "%[EXIF:Orientation]\n" path)]
|
||||||
(if (and (= 0 (:exit dim-result))
|
(when (= 0 (:exit dim-result))
|
||||||
(= 0 (:exit orient-result)))
|
|
||||||
(let [[w h] (-> (:out dim-result)
|
(let [[w h] (-> (:out dim-result)
|
||||||
str/trim
|
str/trim
|
||||||
(clojure.string/split #"\s+")
|
(clojure.string/split #"\s+")
|
||||||
(->> (mapv #(Integer/parseInt %))))
|
(->> (mapv #(Integer/parseInt %))))
|
||||||
orientation (-> orient-result :out str/trim)]
|
orientation-exit (:exit orient-result)
|
||||||
(case orientation
|
orientation (-> orient-result :out str/trim)]
|
||||||
("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees
|
(if (= 0 orientation-exit)
|
||||||
{:width w :height h})) ; Normal or unknown orientation
|
(case orientation
|
||||||
nil)))
|
("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees
|
||||||
|
{:width w :height h}) ; Normal or unknown orientation
|
||||||
|
{:width w :height h}))))) ; If orientation can't be read, use dimensions as-is
|
||||||
|
|
||||||
(defmethod process :info
|
(defmethod process :info
|
||||||
[{:keys [input] :as params}]
|
[{:keys [input] :as params}]
|
||||||
@@ -247,26 +247,37 @@
|
|||||||
:hint "uploaded svg does not provides dimensions"))
|
:hint "uploaded svg does not provides dimensions"))
|
||||||
(merge input info {:ts (ct/now) :size (fs/size path)}))
|
(merge input info {:ts (ct/now) :size (fs/size path)}))
|
||||||
|
|
||||||
(let [instance (Info. (str path))
|
(let [path-str (str path)
|
||||||
mtype' (.getProperty instance "Mime type")]
|
identify-res (sh/sh "identify" "-format" "image/%[magick]\n" path-str)
|
||||||
|
;; identify prints one line per frame (animated GIFs, etc.); we take the first one
|
||||||
|
mtype' (if (zero? (:exit identify-res))
|
||||||
|
(-> identify-res
|
||||||
|
:out
|
||||||
|
str/trim
|
||||||
|
(str/split #"\s+" 2)
|
||||||
|
first
|
||||||
|
str/lower)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :invalid-image
|
||||||
|
:hint "invalid image"))
|
||||||
|
{:keys [width height]}
|
||||||
|
(or (get-dimensions-with-orientation path-str)
|
||||||
|
(do
|
||||||
|
(l/warn "Failed to read image dimensions with orientation" {:path path})
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :invalid-image
|
||||||
|
:hint "invalid image")))]
|
||||||
(when (and (string? mtype)
|
(when (and (string? mtype)
|
||||||
(not= mtype mtype'))
|
(not= (str/lower mtype) mtype'))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :media-type-mismatch
|
:code :media-type-mismatch
|
||||||
:hint (str "Seems like you are uploading a file whose content does not match the extension."
|
:hint (str "Seems like you are uploading a file whose content does not match the extension."
|
||||||
"Expected: " mtype ". Got: " mtype')))
|
"Expected: " mtype ". Got: " mtype')))
|
||||||
(let [{:keys [width height]}
|
(assoc input
|
||||||
(or (get-dimensions-with-orientation (str path))
|
:width width
|
||||||
(do
|
:height height
|
||||||
(l/warn "Failed to read image dimensions with orientation; falling back to im4java"
|
:size (fs/size path)
|
||||||
{:path path})
|
:ts (ct/now))))))
|
||||||
{:width (.getPageWidth instance)
|
|
||||||
:height (.getPageHeight instance)}))]
|
|
||||||
(assoc input
|
|
||||||
:width width
|
|
||||||
:height height
|
|
||||||
:size (fs/size path)
|
|
||||||
:ts (ct/now)))))))
|
|
||||||
|
|
||||||
(defmethod process-error org.im4java.core.InfoException
|
(defmethod process-error org.im4java.core.InfoException
|
||||||
[error]
|
[error]
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ RUN set -e; \
|
|||||||
libltdl-dev \
|
libltdl-dev \
|
||||||
liblzma-dev \
|
liblzma-dev \
|
||||||
libopenexr-dev \
|
libopenexr-dev \
|
||||||
|
libxml2-dev \
|
||||||
libpng-dev \
|
libpng-dev \
|
||||||
librsvg2-dev \
|
librsvg2-dev \
|
||||||
libtiff-dev \
|
libtiff-dev \
|
||||||
@@ -52,6 +53,7 @@ RUN set -e; \
|
|||||||
libfftw3-dev \
|
libfftw3-dev \
|
||||||
libheif-dev \
|
libheif-dev \
|
||||||
libjpeg-dev \
|
libjpeg-dev \
|
||||||
|
libxml2-dev \
|
||||||
liblcms2-dev \
|
liblcms2-dev \
|
||||||
libltdl-dev \
|
libltdl-dev \
|
||||||
liblzma-dev \
|
liblzma-dev \
|
||||||
@@ -77,6 +79,7 @@ RUN set -e; \
|
|||||||
libopenjp2-7 \
|
libopenjp2-7 \
|
||||||
libpng16-16 \
|
libpng16-16 \
|
||||||
librsvg2-2 \
|
librsvg2-2 \
|
||||||
|
libxml2 \
|
||||||
libtiff6 \
|
libtiff6 \
|
||||||
libwebp7 \
|
libwebp7 \
|
||||||
libwebpdemux2 \
|
libwebpdemux2 \
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ RUN set -ex; \
|
|||||||
|
|
||||||
COPY --from=build /opt/jre /opt/jre
|
COPY --from=build /opt/jre /opt/jre
|
||||||
COPY --from=build /opt/node /opt/node
|
COPY --from=build /opt/node /opt/node
|
||||||
COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick
|
COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick
|
||||||
|
|
||||||
ARG BUNDLE_PATH="./bundle-backend/"
|
ARG BUNDLE_PATH="./bundle-backend/"
|
||||||
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/backend/
|
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/backend/
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ RUN set -eux; \
|
|||||||
|
|
||||||
ARG BUNDLE_PATH="./bundle-exporter/"
|
ARG BUNDLE_PATH="./bundle-exporter/"
|
||||||
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/exporter/
|
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/exporter/
|
||||||
COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick
|
COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick
|
||||||
|
|
||||||
WORKDIR /opt/penpot/exporter
|
WORKDIR /opt/penpot/exporter
|
||||||
USER penpot:penpot
|
USER penpot:penpot
|
||||||
|
|||||||
@@ -38,6 +38,24 @@
|
|||||||
(assoc :path "/render.html")
|
(assoc :path "/render.html")
|
||||||
(assoc :query (u/map->query-string params)))))
|
(assoc :query (u/map->query-string params)))))
|
||||||
|
|
||||||
|
(sync-page-size! [dom]
|
||||||
|
(bw/eval! dom
|
||||||
|
(fn [elem]
|
||||||
|
;; IMPORTANT: No CLJS runtime allowed. Use only JS
|
||||||
|
;; primitives. This runs in a context without access to
|
||||||
|
;; cljs.core. Avoid any functions that transpile to
|
||||||
|
;; cljs.core/* calls, as they will break in the browser
|
||||||
|
;; runtime.
|
||||||
|
|
||||||
|
(let [width (.getAttribute ^js elem "width")
|
||||||
|
height (.getAttribute ^js elem "height")
|
||||||
|
style-node (let [node (.createElement js/document "style")]
|
||||||
|
(.appendChild (.-head js/document) node)
|
||||||
|
node)]
|
||||||
|
(set! (.-textContent style-node)
|
||||||
|
(dm/str "@page { size: " width "px " height "px; margin: 0; }\n"
|
||||||
|
"html, body, #app { margin: 0; padding: 0; width: " width "px; height: " height "px; overflow: visible; }"))))))
|
||||||
|
|
||||||
(render-object [page base-uri {:keys [id] :as object}]
|
(render-object [page base-uri {:keys [id] :as object}]
|
||||||
(p/let [uri (prepare-uri base-uri id)
|
(p/let [uri (prepare-uri base-uri id)
|
||||||
path (sh/tempfile :prefix "penpot.tmp.pdf." :suffix (mime/get-extension type))]
|
path (sh/tempfile :prefix "penpot.tmp.pdf." :suffix (mime/get-extension type))]
|
||||||
@@ -45,6 +63,7 @@
|
|||||||
(bw/nav! page uri)
|
(bw/nav! page uri)
|
||||||
(p/let [dom (bw/select page (dm/str "#screenshot-" id))]
|
(p/let [dom (bw/select page (dm/str "#screenshot-" id))]
|
||||||
(bw/wait-for dom)
|
(bw/wait-for dom)
|
||||||
|
(sync-page-size! dom)
|
||||||
(bw/screenshot dom {:full-page? true})
|
(bw/screenshot dom {:full-page? true})
|
||||||
(bw/sleep page 2000) ; the good old fix with sleep
|
(bw/sleep page 2000) ; the good old fix with sleep
|
||||||
(bw/pdf page {:path path})
|
(bw/pdf page {:path path})
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
(def ^function create-editor editor.v2/create)
|
(def ^function create-editor editor.v2/create)
|
||||||
(def ^function set-editor-root! editor.v2/setRoot)
|
(def ^function set-editor-root! editor.v2/setRoot)
|
||||||
(def ^function get-editor-root editor.v2/getRoot)
|
(def ^function get-editor-root editor.v2/getRoot)
|
||||||
|
(def ^function is-empty? editor.v2/isEmpty)
|
||||||
(def ^function dispose! editor.v2/dispose)
|
(def ^function dispose! editor.v2/dispose)
|
||||||
|
|
||||||
(declare v2-update-text-shape-content)
|
(declare v2-update-text-shape-content)
|
||||||
@@ -901,15 +902,22 @@
|
|||||||
(update-in state [:workspace-text-modifier shape-id] {:position-data position-data}))))
|
(update-in state [:workspace-text-modifier shape-id] {:position-data position-data}))))
|
||||||
|
|
||||||
(defn v2-update-text-shape-content
|
(defn v2-update-text-shape-content
|
||||||
[id content & {:keys [update-name? name finalize?]
|
[id content & {:keys [update-name? name finalize? save-undo?]
|
||||||
:or {update-name? false name nil finalize? false}}]
|
:or {update-name? false name nil finalize? false save-undo? true}}]
|
||||||
(ptk/reify ::v2-update-text-shape-content
|
(ptk/reify ::v2-update-text-shape-content
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(if (features/active-feature? state "render-wasm/v1")
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
(let [objects (dsh/lookup-page-objects state)
|
(let [objects (dsh/lookup-page-objects state)
|
||||||
shape (get objects id)
|
shape (get objects id)
|
||||||
new-shape? (nil? (:content shape))]
|
new-shape? (nil? (:content shape))
|
||||||
|
prev-content (:content shape)
|
||||||
|
has-prev-content? (not (nil? (:prev-content shape)))
|
||||||
|
has-content? (when-not new-shape?
|
||||||
|
(v2-content-has-text? content))
|
||||||
|
did-has-content? (when-not new-shape?
|
||||||
|
(v2-content-has-text? prev-content))]
|
||||||
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(rx/of
|
(rx/of
|
||||||
(dwsh/update-shapes
|
(dwsh/update-shapes
|
||||||
@@ -917,10 +925,16 @@
|
|||||||
(fn [shape]
|
(fn [shape]
|
||||||
(let [new-shape (-> shape
|
(let [new-shape (-> shape
|
||||||
(assoc :content content)
|
(assoc :content content)
|
||||||
|
(cond-> (and has-content?
|
||||||
|
has-prev-content?)
|
||||||
|
(dissoc :prev-content))
|
||||||
|
(cond-> (and did-has-content?
|
||||||
|
(not has-content?))
|
||||||
|
(assoc :prev-content prev-content))
|
||||||
(cond-> (and update-name? (some? name))
|
(cond-> (and update-name? (some? name))
|
||||||
(assoc :name name)))]
|
(assoc :name name)))]
|
||||||
new-shape))
|
new-shape))
|
||||||
{:undo-group (when new-shape? id)})
|
{:save-undo? save-undo? :undo-group (when new-shape? id)})
|
||||||
|
|
||||||
(if (and (not= :fixed (:grow-type shape)) finalize?)
|
(if (and (not= :fixed (:grow-type shape)) finalize?)
|
||||||
(dwm/apply-wasm-modifiers
|
(dwm/apply-wasm-modifiers
|
||||||
@@ -933,8 +947,16 @@
|
|||||||
|
|
||||||
(when finalize?
|
(when finalize?
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(when (and (not (v2-content-has-text? content)) (some? id))
|
(when (and (not has-content?) (some? id))
|
||||||
(rx/of
|
(rx/of
|
||||||
|
(when has-prev-content?
|
||||||
|
(dwsh/update-shapes
|
||||||
|
[id]
|
||||||
|
(fn [shape]
|
||||||
|
(let [new-shape (-> shape
|
||||||
|
(assoc :content (:prev-content shape)))]
|
||||||
|
new-shape))
|
||||||
|
{:save-undo? false}))
|
||||||
(dws/deselect-shape id)
|
(dws/deselect-shape id)
|
||||||
(dwsh/delete-shapes #{id})))
|
(dwsh/delete-shapes #{id})))
|
||||||
(rx/of (dwt/finish-transform))))))
|
(rx/of (dwt/finish-transform))))))
|
||||||
|
|||||||
@@ -621,7 +621,7 @@
|
|||||||
(->> stream
|
(->> stream
|
||||||
(rx/filter (ptk/type? ::dws/duplicate-selected))
|
(rx/filter (ptk/type? ::dws/duplicate-selected))
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/map #(start-move from-position))))))
|
(rx/map #(start-move from-position nil true))))))
|
||||||
|
|
||||||
(defn get-drop-cell
|
(defn get-drop-cell
|
||||||
[target-frame objects position]
|
[target-frame objects position]
|
||||||
@@ -641,8 +641,9 @@
|
|||||||
(dom/set-property! node "transform" (gmt/translate-matrix move-vector))))))
|
(dom/set-property! node "transform" (gmt/translate-matrix move-vector))))))
|
||||||
|
|
||||||
(defn start-move
|
(defn start-move
|
||||||
([from-position] (start-move from-position nil))
|
([from-position] (start-move from-position nil false))
|
||||||
([from-position ids]
|
([from-position ids] (start-move from-position ids false))
|
||||||
|
([from-position ids from-duplicate?]
|
||||||
(ptk/reify ::start-move
|
(ptk/reify ::start-move
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
@@ -750,38 +751,47 @@
|
|||||||
(rx/share))]
|
(rx/share))]
|
||||||
|
|
||||||
(if (features/active-feature? state "render-wasm/v1")
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
(rx/merge
|
(let [duplicate-stopper
|
||||||
(->> modifiers-stream
|
(->> ms/mouse-position-alt
|
||||||
(rx/map
|
(rx/mapcat
|
||||||
(fn [[modifiers snap-ignore-axis]]
|
(fn [alt?]
|
||||||
(dwm/set-wasm-modifiers modifiers :snap-ignore-axis snap-ignore-axis))))
|
(if (and alt? (not from-duplicate?))
|
||||||
|
(rx/of true)
|
||||||
|
(rx/empty)))))]
|
||||||
|
(rx/merge
|
||||||
|
(->> modifiers-stream
|
||||||
|
(rx/take-until duplicate-stopper)
|
||||||
|
(rx/map
|
||||||
|
(fn [[modifiers snap-ignore-axis]]
|
||||||
|
(dwm/set-wasm-modifiers modifiers :snap-ignore-axis snap-ignore-axis))))
|
||||||
|
|
||||||
(->> move-stream
|
(->> move-stream
|
||||||
(rx/with-latest-from ms/mouse-position-alt)
|
(rx/with-latest-from ms/mouse-position-alt)
|
||||||
(rx/filter (fn [[_ alt?]] alt?))
|
(rx/filter (fn [[_ alt?]] alt?))
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/mapcat
|
(rx/mapcat
|
||||||
(fn [[_ alt?]]
|
(fn [[_ alt?]]
|
||||||
(if (and (not duplicate-move-started?) alt?)
|
(if (and (not from-duplicate?) alt?)
|
||||||
(rx/of (start-move-duplicate from-position)
|
(rx/of (start-move-duplicate from-position)
|
||||||
(dws/duplicate-selected false true))
|
(dws/duplicate-selected false true))
|
||||||
(rx/empty)))))
|
(rx/empty)))))
|
||||||
|
|
||||||
;; Last event will write the modifiers creating the changes
|
;; Last event will write the modifiers creating the changes
|
||||||
(->> move-stream
|
(->> move-stream
|
||||||
(rx/last)
|
(rx/last)
|
||||||
(rx/with-latest-from modifiers-stream)
|
(rx/take-until duplicate-stopper)
|
||||||
(rx/mapcat
|
(rx/with-latest-from modifiers-stream)
|
||||||
(fn [[[_ target-frame drop-index drop-cell] [modifiers snap-ignore-axis]]]
|
(rx/mapcat
|
||||||
(let [undo-id (js/Symbol)]
|
(fn [[[_ target-frame drop-index drop-cell] [modifiers snap-ignore-axis]]]
|
||||||
(rx/of
|
(let [undo-id (js/Symbol)]
|
||||||
(dwu/start-undo-transaction undo-id)
|
(rx/of
|
||||||
(dwm/apply-wasm-modifiers modifiers
|
(dwu/start-undo-transaction undo-id)
|
||||||
:snap-ignore-axis snap-ignore-axis
|
(dwm/apply-wasm-modifiers modifiers
|
||||||
:undo-transation? false)
|
:snap-ignore-axis snap-ignore-axis
|
||||||
(move-shapes-to-frame ids target-frame drop-index drop-cell)
|
:undo-transation? false)
|
||||||
(finish-transform)
|
(move-shapes-to-frame ids target-frame drop-index drop-cell)
|
||||||
(dwu/commit-undo-transaction undo-id)))))))
|
(finish-transform)
|
||||||
|
(dwu/commit-undo-transaction undo-id))))))))
|
||||||
|
|
||||||
(rx/merge
|
(rx/merge
|
||||||
(->> modifiers-stream
|
(->> modifiers-stream
|
||||||
|
|||||||
@@ -117,7 +117,8 @@
|
|||||||
(st/emit! (dwt/v2-update-text-shape-content shape-id content
|
(st/emit! (dwt/v2-update-text-shape-content shape-id content
|
||||||
:update-name? update-name?
|
:update-name? update-name?
|
||||||
:name generated-name
|
:name generated-name
|
||||||
:finalize? true))))
|
:finalize? true
|
||||||
|
:save-undo? false))))
|
||||||
|
|
||||||
(let [container-node (mf/ref-val container-ref)]
|
(let [container-node (mf/ref-val container-ref)]
|
||||||
(dom/set-style! container-node "opacity" 0)))
|
(dom/set-style! container-node "opacity" 0)))
|
||||||
@@ -135,15 +136,21 @@
|
|||||||
on-needs-layout
|
on-needs-layout
|
||||||
(fn []
|
(fn []
|
||||||
(when-let [content (content/dom->cljs (dwt/get-editor-root instance))]
|
(when-let [content (content/dom->cljs (dwt/get-editor-root instance))]
|
||||||
(st/emit! (dwt/v2-update-text-shape-content shape-id content :update-name? true)))
|
(st/emit! (dwt/v2-update-text-shape-content shape-id content
|
||||||
|
:update-name? true
|
||||||
|
:save-undo? false)))
|
||||||
;; FIXME: We need to find a better way to trigger layout changes.
|
;; FIXME: We need to find a better way to trigger layout changes.
|
||||||
#_(st/emit!
|
#_(st/emit!
|
||||||
(dwt/v2-update-text-shape-position-data shape-id [])))
|
(dwt/v2-update-text-shape-position-data shape-id [])))
|
||||||
|
|
||||||
on-change
|
on-change
|
||||||
(fn []
|
(fn []
|
||||||
(when-let [content (content/dom->cljs (dwt/get-editor-root instance))]
|
(let [is-empty? (dwt/is-empty? instance)
|
||||||
(st/emit! (dwt/v2-update-text-shape-content shape-id content :update-name? true))))
|
save-undo? (not is-empty?)]
|
||||||
|
(when-let [content (content/dom->cljs (dwt/get-editor-root instance))]
|
||||||
|
(st/emit! (dwt/v2-update-text-shape-content shape-id content
|
||||||
|
:update-name? true
|
||||||
|
:save-undo? save-undo?)))))
|
||||||
|
|
||||||
on-clipboard-change
|
on-clipboard-change
|
||||||
(fn [event]
|
(fn [event]
|
||||||
@@ -247,7 +254,7 @@
|
|||||||
:ref container-ref
|
:ref container-ref
|
||||||
:data-testid "text-editor-container"
|
:data-testid "text-editor-container"
|
||||||
:style {:width "var(--editor-container-width)"
|
:style {:width "var(--editor-container-width)"
|
||||||
:height "var(--editor-container-height)"}
|
:height "var(--editor-container-height)"}}
|
||||||
;; We hide the editor when is blurred because otherwise the
|
;; We hide the editor when is blurred because otherwise the
|
||||||
;; selection won't let us see the underlying text. Use opacity
|
;; selection won't let us see the underlying text. Use opacity
|
||||||
;; because display or visibility won't allow to recover focus
|
;; because display or visibility won't allow to recover focus
|
||||||
@@ -256,7 +263,7 @@
|
|||||||
;; IMPORTANT! This is now done through DOM mutations (see
|
;; IMPORTANT! This is now done through DOM mutations (see
|
||||||
;; on-blur and on-focus) but I keep this for future references.
|
;; on-blur and on-focus) but I keep this for future references.
|
||||||
;; :opacity (when @blurred 0)}}
|
;; :opacity (when @blurred 0)}}
|
||||||
}
|
|
||||||
[:div
|
[:div
|
||||||
{:class (dm/str
|
{:class (dm/str
|
||||||
"mousetrap "
|
"mousetrap "
|
||||||
|
|||||||
@@ -911,17 +911,23 @@
|
|||||||
|
|
||||||
(def render-finish
|
(def render-finish
|
||||||
(letfn [(do-render [ts]
|
(letfn [(do-render [ts]
|
||||||
(perf/begin-measure "render-finish")
|
;; Check if context is still initialized before executing
|
||||||
(h/call wasm/internal-module "_set_view_end")
|
;; to prevent errors when navigating quickly
|
||||||
(render ts)
|
(when wasm/context-initialized?
|
||||||
(perf/end-measure "render-finish"))]
|
(perf/begin-measure "render-finish")
|
||||||
|
(h/call wasm/internal-module "_set_view_end")
|
||||||
|
(render ts)
|
||||||
|
(perf/end-measure "render-finish")))]
|
||||||
(fns/debounce do-render DEBOUNCE_DELAY_MS)))
|
(fns/debounce do-render DEBOUNCE_DELAY_MS)))
|
||||||
|
|
||||||
(def render-pan
|
(def render-pan
|
||||||
(letfn [(do-render-pan [ts]
|
(letfn [(do-render-pan [ts]
|
||||||
(perf/begin-measure "render-pan")
|
;; Check if context is still initialized before executing
|
||||||
(render ts)
|
;; to prevent errors when navigating quickly
|
||||||
(perf/end-measure "render-pan"))]
|
(when wasm/context-initialized?
|
||||||
|
(perf/begin-measure "render-pan")
|
||||||
|
(render ts)
|
||||||
|
(perf/end-measure "render-pan")))]
|
||||||
(fns/throttle do-render-pan THROTTLE_DELAY_MS)))
|
(fns/throttle do-render-pan THROTTLE_DELAY_MS)))
|
||||||
|
|
||||||
(defn set-view-box
|
(defn set-view-box
|
||||||
@@ -1399,6 +1405,16 @@
|
|||||||
[]
|
[]
|
||||||
(when wasm/context-initialized?
|
(when wasm/context-initialized?
|
||||||
(try
|
(try
|
||||||
|
;; Cancel any pending animation frame to prevent race conditions
|
||||||
|
(when wasm/internal-frame-id
|
||||||
|
(js/cancelAnimationFrame wasm/internal-frame-id)
|
||||||
|
(set! wasm/internal-frame-id nil))
|
||||||
|
|
||||||
|
;; Reset render flags to prevent new renders from being scheduled
|
||||||
|
(reset! pending-render false)
|
||||||
|
(reset! shapes-loading? false)
|
||||||
|
(reset! deferred-render? false)
|
||||||
|
|
||||||
;; TODO: perform corresponding cleaning
|
;; TODO: perform corresponding cleaning
|
||||||
(set! wasm/context-initialized? false)
|
(set! wasm/context-initialized? false)
|
||||||
(h/call wasm/internal-module "_clean_up")
|
(h/call wasm/internal-module "_clean_up")
|
||||||
|
|||||||
@@ -32,7 +32,8 @@
|
|||||||
"This function adds units to style values"
|
"This function adds units to style values"
|
||||||
[k v]
|
[k v]
|
||||||
(cond
|
(cond
|
||||||
(and (or (= k :font-size)
|
(and (keyword? k)
|
||||||
|
(or (= k :font-size)
|
||||||
(= k :letter-spacing))
|
(= k :letter-spacing))
|
||||||
(not= (str/slice v -2) "px"))
|
(not= (str/slice v -2) "px"))
|
||||||
(str v "px")
|
(str v "px")
|
||||||
|
|||||||
@@ -326,7 +326,9 @@ export class TextEditor extends EventTarget {
|
|||||||
* @param {FocusEvent} e
|
* @param {FocusEvent} e
|
||||||
*/
|
*/
|
||||||
#onBlur = (e) => {
|
#onBlur = (e) => {
|
||||||
this.#changeController.notifyImmediately();
|
if (!this.isEmpty) {
|
||||||
|
this.#changeController.notifyImmediately();
|
||||||
|
}
|
||||||
this.#selectionController.saveSelection();
|
this.#selectionController.saveSelection();
|
||||||
this.dispatchEvent(new FocusEvent(e.type, e));
|
this.dispatchEvent(new FocusEvent(e.type, e));
|
||||||
};
|
};
|
||||||
@@ -683,13 +685,26 @@ export function createRootFromString(string) {
|
|||||||
* Returns true if the passed object is a TextEditor
|
* Returns true if the passed object is a TextEditor
|
||||||
* instance.
|
* instance.
|
||||||
*
|
*
|
||||||
* @param {*} instance
|
* @param {TextEditor} instance
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isTextEditor(instance) {
|
export function isTextEditor(instance) {
|
||||||
return instance instanceof TextEditor;
|
return instance instanceof TextEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the TextEditor is empty.
|
||||||
|
*
|
||||||
|
* @param {TextEditor} instance
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isEmpty(instance) {
|
||||||
|
if (isTextEditor(instance)) {
|
||||||
|
return instance.isEmpty;
|
||||||
|
}
|
||||||
|
throw new TypeError('Instance is not a TextEditor');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the root element of a TextEditor
|
* Returns the root element of a TextEditor
|
||||||
* instance.
|
* instance.
|
||||||
@@ -701,7 +716,7 @@ export function getRoot(instance) {
|
|||||||
if (isTextEditor(instance)) {
|
if (isTextEditor(instance)) {
|
||||||
return instance.root;
|
return instance.root;
|
||||||
}
|
}
|
||||||
return null;
|
throw new TypeError("Instance is not a TextEditor");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -714,9 +729,9 @@ export function getRoot(instance) {
|
|||||||
export function setRoot(instance, root) {
|
export function setRoot(instance, root) {
|
||||||
if (isTextEditor(instance)) {
|
if (isTextEditor(instance)) {
|
||||||
instance.root = root;
|
instance.root = root;
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
throw new TypeError("Instance is not a TextEditor");
|
||||||
return instance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -741,7 +756,7 @@ export function getCurrentStyle(instance) {
|
|||||||
if (isTextEditor(instance)) {
|
if (isTextEditor(instance)) {
|
||||||
return instance.currentStyle;
|
return instance.currentStyle;
|
||||||
}
|
}
|
||||||
return null;
|
throw new TypeError("Instance is not a TextEditor");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -756,7 +771,7 @@ export function applyStylesToSelection(instance, styles) {
|
|||||||
if (isTextEditor(instance)) {
|
if (isTextEditor(instance)) {
|
||||||
return instance.applyStylesToSelection(styles);
|
return instance.applyStylesToSelection(styles);
|
||||||
}
|
}
|
||||||
return null;
|
throw new TypeError("Instance is not a TextEditor");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -770,7 +785,7 @@ export function dispose(instance) {
|
|||||||
if (isTextEditor(instance)) {
|
if (isTextEditor(instance)) {
|
||||||
return instance.dispose();
|
return instance.dispose();
|
||||||
}
|
}
|
||||||
return null;
|
throw new TypeError("Instance is not a TextEditor");
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TextEditor;
|
export default TextEditor;
|
||||||
|
|||||||
@@ -54,8 +54,12 @@ export class ChangeController extends EventTarget {
|
|||||||
return this.#hasPendingChanges;
|
return this.#hasPendingChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles timeout.
|
||||||
|
*/
|
||||||
#onTimeout = () => {
|
#onTimeout = () => {
|
||||||
this.dispatchEvent(new Event("change"));
|
this.dispatchEvent(new Event("change"));
|
||||||
|
this.#hasPendingChanges = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user