mirror of
https://github.com/penpot/penpot.git
synced 2025-05-06 00:25:54 +02:00
✨ Changes to text editor
This commit is contained in:
parent
d83459f674
commit
618d22d214
9 changed files with 115 additions and 149 deletions
|
@ -1,62 +1,61 @@
|
||||||
foreignObject {
|
|
||||||
.text-editor,
|
|
||||||
.rich-text {
|
|
||||||
color: $color-black;
|
|
||||||
height: 100%;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-family: sourcesanspro;
|
|
||||||
|
|
||||||
div {
|
.text-editor,
|
||||||
line-height: inherit;
|
.rich-text {
|
||||||
user-select: text;
|
color: $color-black;
|
||||||
}
|
height: 100%;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: sourcesanspro;
|
||||||
|
|
||||||
span {
|
div {
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
}
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-editor {
|
span {
|
||||||
.DraftEditor-root {
|
line-height: inherit;
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.align-top {
|
|
||||||
.DraftEditor-root {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.align-center {
|
|
||||||
.DraftEditor-root {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.align-bottom {
|
|
||||||
.DraftEditor-root {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.rich-text .paragraphs {
|
.text-editor {
|
||||||
|
.DraftEditor-root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
&.align-top {
|
&.align-top {
|
||||||
|
.DraftEditor-root {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.align-center {
|
&.align-center {
|
||||||
|
.DraftEditor-root {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.align-bottom {
|
&.align-bottom {
|
||||||
|
.DraftEditor-root {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rich-text .paragraphs {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&.align-top {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.align-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.align-bottom {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
(def pointer-move (cursor-ref :pointer-move 0 0 10 42))
|
(def pointer-move (cursor-ref :pointer-move 0 0 10 42))
|
||||||
(def pointer-node (cursor-ref :pointer-node 0 0 10 32))
|
(def pointer-node (cursor-ref :pointer-node 0 0 10 32))
|
||||||
(def resize-alt (cursor-ref :resize-alt))
|
(def resize-alt (cursor-ref :resize-alt))
|
||||||
(def text (cursor-ref :text))
|
|
||||||
(def zoom (cursor-ref :zoom))
|
(def zoom (cursor-ref :zoom))
|
||||||
(def zoom-in (cursor-ref :zoom-in))
|
(def zoom-in (cursor-ref :zoom-in))
|
||||||
(def zoom-out (cursor-ref :zoom-out))
|
(def zoom-out (cursor-ref :zoom-out))
|
||||||
|
@ -40,6 +39,7 @@
|
||||||
(def resize-ns (cursor-fn :resize-h 90))
|
(def resize-ns (cursor-fn :resize-h 90))
|
||||||
(def resize-nwse (cursor-fn :resize-h 135))
|
(def resize-nwse (cursor-fn :resize-h 135))
|
||||||
(def rotate (cursor-fn :rotate 90))
|
(def rotate (cursor-fn :rotate 90))
|
||||||
|
(def text (cursor-fn :text 0))
|
||||||
|
|
||||||
;;
|
;;
|
||||||
(def resize-ew-2 (cursor-fn :resize-h-2 0))
|
(def resize-ew-2 (cursor-fn :resize-h-2 0))
|
||||||
|
|
|
@ -80,34 +80,38 @@
|
||||||
"z")}))
|
"z")}))
|
||||||
attrs))
|
attrs))
|
||||||
|
|
||||||
(defn add-fill [attrs shape render-id index]
|
(defn add-fill
|
||||||
(let [
|
([attrs shape render-id]
|
||||||
fill-attrs (cond
|
(add-fill attrs shape render-id 0))
|
||||||
(contains? shape :fill-image)
|
|
||||||
(let [fill-image-id (str "fill-image-" render-id)]
|
|
||||||
{:fill (str/format "url(#%s)" fill-image-id)})
|
|
||||||
|
|
||||||
(contains? shape :fill-color-gradient)
|
([attrs shape render-id index]
|
||||||
(let [fill-color-gradient-id (str "fill-color-gradient_" render-id "_" index)]
|
(let [fill-attrs
|
||||||
{:fill (str/format "url(#%s)" fill-color-gradient-id)})
|
(cond
|
||||||
|
(contains? shape :fill-image)
|
||||||
|
(let [fill-image-id (str "fill-image-" render-id)]
|
||||||
|
{:fill (str/format "url(#%s)" fill-image-id)})
|
||||||
|
|
||||||
(contains? shape :fill-color)
|
(contains? shape :fill-color-gradient)
|
||||||
{:fill (:fill-color shape)}
|
(let [fill-color-gradient-id (str "fill-color-gradient_" render-id "_" index)]
|
||||||
|
{:fill (str/format "url(#%s)" fill-color-gradient-id)})
|
||||||
|
|
||||||
;; If contains svg-attrs the origin is svg. If it's not svg origin
|
(contains? shape :fill-color)
|
||||||
;; we setup the default fill as transparent (instead of black)
|
{:fill (:fill-color shape)}
|
||||||
(and (not (contains? shape :svg-attrs))
|
|
||||||
(not (#{:svg-raw :group} (:type shape))))
|
|
||||||
{:fill "none"}
|
|
||||||
|
|
||||||
:else
|
;; If contains svg-attrs the origin is svg. If it's not svg origin
|
||||||
{})
|
;; we setup the default fill as transparent (instead of black)
|
||||||
|
(and (not (contains? shape :svg-attrs))
|
||||||
|
(not (#{:svg-raw :group} (:type shape))))
|
||||||
|
{:fill "none"}
|
||||||
|
|
||||||
fill-attrs (cond-> fill-attrs
|
:else
|
||||||
(contains? shape :fill-opacity)
|
{})
|
||||||
(assoc :fillOpacity (:fill-opacity shape)))]
|
|
||||||
|
|
||||||
(obj/merge! attrs (clj->js fill-attrs))))
|
fill-attrs (cond-> fill-attrs
|
||||||
|
(contains? shape :fill-opacity)
|
||||||
|
(assoc :fillOpacity (:fill-opacity shape)))]
|
||||||
|
|
||||||
|
(obj/merge! attrs (clj->js fill-attrs)))))
|
||||||
|
|
||||||
(defn add-stroke [attrs shape render-id]
|
(defn add-stroke [attrs shape render-id]
|
||||||
(let [stroke-style (:stroke-style shape :none)
|
(let [stroke-style (:stroke-style shape :none)
|
||||||
|
|
|
@ -38,19 +38,8 @@
|
||||||
stroke-width (case (:stroke-alignment shape :center)
|
stroke-width (case (:stroke-alignment shape :center)
|
||||||
:center (/ (:stroke-width shape 0) 2)
|
:center (/ (:stroke-width shape 0) 2)
|
||||||
:outer (:stroke-width shape 0)
|
:outer (:stroke-width shape 0)
|
||||||
0)
|
0)]
|
||||||
margin (gsh/shape-stroke-margin shape stroke-width)
|
[:mask {:id stroke-mask-id}
|
||||||
bounding-box (-> (gsh/points->selrect (:points shape))
|
|
||||||
(update :x - (+ stroke-width margin))
|
|
||||||
(update :y - (+ stroke-width margin))
|
|
||||||
(update :width + (* 2 (+ stroke-width margin)))
|
|
||||||
(update :height + (* 2 (+ stroke-width margin))))]
|
|
||||||
[:mask {:id stroke-mask-id
|
|
||||||
:x (:x bounding-box)
|
|
||||||
:y (:y bounding-box)
|
|
||||||
:width (:width bounding-box)
|
|
||||||
:height (:height bounding-box)
|
|
||||||
:maskUnits "userSpaceOnUse"}
|
|
||||||
[:use {:xlinkHref (str "#" shape-id)
|
[:use {:xlinkHref (str "#" shape-id)
|
||||||
:style #js {:fill "none" :stroke "white" :strokeWidth (* stroke-width 2)}}]
|
:style #js {:fill "none" :stroke "white" :strokeWidth (* stroke-width 2)}}]
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,12 @@
|
||||||
(let [valign (:vertical-align node "top")
|
(let [valign (:vertical-align node "top")
|
||||||
base #js {:height "100%"
|
base #js {:height "100%"
|
||||||
:width "100%"
|
:width "100%"
|
||||||
:fontFamily "sourcesanspro"}]
|
:fontFamily "sourcesanspro"
|
||||||
|
:display "flex"}]
|
||||||
(cond-> base
|
(cond-> base
|
||||||
(= valign "top") (obj/set! "justifyContent" "flex-start")
|
(= valign "top") (obj/set! "alignItems" "flex-start")
|
||||||
(= valign "center") (obj/set! "justifyContent" "center")
|
(= valign "center") (obj/set! "alignItems" "center")
|
||||||
(= valign "bottom") (obj/set! "justifyContent" "flex-end"))))
|
(= valign "bottom") (obj/set! "alignItems" "flex-end"))))
|
||||||
|
|
||||||
(defn generate-paragraph-set-styles
|
(defn generate-paragraph-set-styles
|
||||||
[{:keys [grow-type] :as shape}]
|
[{:keys [grow-type] :as shape}]
|
||||||
|
@ -39,7 +40,6 @@
|
||||||
#js {:display "inline-flex"
|
#js {:display "inline-flex"
|
||||||
:flexDirection "column"
|
:flexDirection "column"
|
||||||
:justifyContent "inherit"
|
:justifyContent "inherit"
|
||||||
:minHeight (when-not (or auto-width? auto-height?) "100%")
|
|
||||||
:minWidth (when-not auto-width? "100%")
|
:minWidth (when-not auto-width? "100%")
|
||||||
:marginRight "1px"
|
:marginRight "1px"
|
||||||
:verticalAlign "top"}))
|
:verticalAlign "top"}))
|
||||||
|
|
|
@ -24,9 +24,8 @@
|
||||||
|
|
||||||
(let [render-id (mf/use-ctx muc/render-ctx)
|
(let [render-id (mf/use-ctx muc/render-ctx)
|
||||||
{:keys [id x y width height position-data] :as shape} (obj/get props "shape")
|
{:keys [id x y width height position-data] :as shape} (obj/get props "shape")
|
||||||
clip-id (str "clip-text" id "_" render-id)
|
transform (str (gsh/transform-matrix shape))
|
||||||
group-props (-> #js {:transform (gsh/transform-matrix shape)
|
group-props (-> #js {:transform transform}
|
||||||
:clipPath (str "url(#" clip-id ")")}
|
|
||||||
(attrs/add-style-attrs shape render-id))
|
(attrs/add-style-attrs shape render-id))
|
||||||
get-gradient-id
|
get-gradient-id
|
||||||
(fn [index]
|
(fn [index]
|
||||||
|
@ -43,9 +42,6 @@
|
||||||
|
|
||||||
[:& shape-custom-stroke {:shape shape}
|
[:& shape-custom-stroke {:shape shape}
|
||||||
[:> :g group-props
|
[:> :g group-props
|
||||||
[:defs
|
|
||||||
[:clipPath {:id clip-id}
|
|
||||||
[:rect.text-clip {:x x :y y :width width :height height}]]]
|
|
||||||
(for [[index data] (d/enumerate position-data)]
|
(for [[index data] (d/enumerate position-data)]
|
||||||
(let [props (-> #js {:x (:x data)
|
(let [props (-> #js {:x (:x data)
|
||||||
:y (:y data)
|
:y (:y data)
|
||||||
|
|
|
@ -131,7 +131,6 @@
|
||||||
handle-change-foreign-object
|
handle-change-foreign-object
|
||||||
(fn [node]
|
(fn [node]
|
||||||
(when (some? node)
|
(when (some? node)
|
||||||
(prn "change!")
|
|
||||||
(let [position-data (utp/calc-position-data node)
|
(let [position-data (utp/calc-position-data node)
|
||||||
parent (dom/get-parent node)
|
parent (dom/get-parent node)
|
||||||
parent-transform (dom/get-attribute parent "transform")
|
parent-transform (dom/get-attribute parent "transform")
|
||||||
|
@ -174,7 +173,7 @@
|
||||||
;; and updates the selrect accordingly
|
;; and updates the selrect accordingly
|
||||||
[:*
|
[:*
|
||||||
[:g.text-shape {:ref on-change-node
|
[:g.text-shape {:ref on-change-node
|
||||||
:opacity (when (or edition? (some? (:position-data shape))) 0.2)
|
:opacity (when (or edition? (some? (:position-data shape))) 0)
|
||||||
:pointer-events "none"}
|
:pointer-events "none"}
|
||||||
|
|
||||||
;; The `:key` prop here is mandatory because the
|
;; The `:key` prop here is mandatory because the
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
(ns app.main.ui.workspace.shapes.text.editor
|
(ns app.main.ui.workspace.shapes.text.editor
|
||||||
(:require
|
(:require
|
||||||
["draft-js" :as draft]
|
["draft-js" :as draft]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.text :as txt]
|
[app.common.text :as txt]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
|
@ -211,7 +212,7 @@
|
||||||
|
|
||||||
[:div.text-editor
|
[:div.text-editor
|
||||||
{:ref self-ref
|
{:ref self-ref
|
||||||
:style {:cursor cur/text
|
:style {:cursor (cur/text (:rotation shape))
|
||||||
:width (:width shape)
|
:width (:width shape)
|
||||||
:height (:height shape)}
|
:height (:height shape)}
|
||||||
:on-click on-click
|
:on-click on-click
|
||||||
|
@ -231,61 +232,34 @@
|
||||||
:ref on-editor
|
:ref on-editor
|
||||||
:editor-state state}]]))
|
:editor-state state}]]))
|
||||||
|
|
||||||
(mf/defc text-shape-edit
|
(defn translate-point-from-viewport
|
||||||
{::mf/wrap [mf/memo]
|
"Translate a point in the viewport into client coordinates"
|
||||||
::mf/wrap-props false
|
[pt viewport zoom]
|
||||||
::mf/forward-ref true}
|
(let [vbox (.. ^js viewport -viewBox -baseVal)
|
||||||
[props _]
|
box (gpt/point (.-x vbox) (.-y vbox))
|
||||||
(let [{:keys [id x y width height grow-type] :as shape} (obj/get props "shape")
|
zoom (gpt/point zoom)]
|
||||||
transform (str (gsh/transform-matrix shape))
|
(-> (gpt/subtract pt box)
|
||||||
|
(gpt/multiply zoom))))
|
||||||
|
|
||||||
clip-id (str "clip-" id)
|
(mf/defc text-editor-viewport
|
||||||
|
{::mf/wrap-props false}
|
||||||
|
[props]
|
||||||
|
(let [shape (obj/get props "shape")
|
||||||
|
viewport-ref (obj/get props "viewport-ref")
|
||||||
|
zoom (obj/get props "zoom")
|
||||||
|
|
||||||
;; local-position-data (mf/use-state nil)
|
position
|
||||||
|
(-> (gpt/point (-> shape :selrect :x)
|
||||||
|
(-> shape :selrect :y))
|
||||||
|
(translate-point-from-viewport (mf/ref-val viewport-ref) zoom))]
|
||||||
|
|
||||||
;;handle-change-foreign-object
|
[:div {:style {:position "absolute"
|
||||||
;;(mf/use-callback
|
:left (str (:x position) "px")
|
||||||
;; (fn [node]
|
:top (str (:y position) "px")
|
||||||
;; (when node
|
:pointer-events "all"
|
||||||
;; (let [position-data (utp/calc-position-data node)]
|
:transform (str (gsh/transform-matrix shape nil (gpt/point 0 0)))
|
||||||
;; (reset! local-position-data position-data)))))
|
:transform-origin "center center"}}
|
||||||
;;
|
|
||||||
;;[shape-ref on-change-node] (use-mutable-observer handle-change-foreign-object)
|
|
||||||
|
|
||||||
;;handle-interaction
|
[:div {:style {:transform (str "scale(" zoom ")")
|
||||||
;;(mf/use-callback
|
:transform-origin "top left"}}
|
||||||
;; (fn []
|
[:& text-shape-edit-html {:shape shape :key (str (:id shape))}]]]))
|
||||||
;; (handle-change-foreign-object (mf/ref-val shape-ref))))
|
|
||||||
]
|
|
||||||
|
|
||||||
#_(mf/use-effect
|
|
||||||
(mf/use-callback handle-interaction)
|
|
||||||
(fn []
|
|
||||||
(let [keys [(events/listen js/document EventType.KEYUP handle-interaction)
|
|
||||||
(events/listen js/document EventType.KEYDOWN handle-interaction)
|
|
||||||
(events/listen js/document EventType.MOUSEDOWN handle-interaction)]]
|
|
||||||
#(doseq [key keys]
|
|
||||||
(events/unlistenByKey key)))))
|
|
||||||
[:*
|
|
||||||
#_[:> shape-container {:shape shape
|
|
||||||
:pointer-events "none"}
|
|
||||||
[:& svg/text-shape {:shape (cond-> shape
|
|
||||||
(some? @local-position-data)
|
|
||||||
(assoc :position-data @local-position-data))}]]
|
|
||||||
|
|
||||||
[:g.text-editor {:clip-path (str "url(#" clip-id ")")
|
|
||||||
;; :ref on-change-node
|
|
||||||
:key (str "editor-" id)}
|
|
||||||
[:defs
|
|
||||||
;; This clippath will cut the huge foreign object we use to calculate the automatic resize
|
|
||||||
[:clipPath {:id clip-id}
|
|
||||||
[:rect {:x x :y y
|
|
||||||
:width (+ width 8) :height (+ height 8)
|
|
||||||
:transform transform}]]]
|
|
||||||
|
|
||||||
[:foreignObject {:transform transform
|
|
||||||
:x x :y y
|
|
||||||
:width (if (#{:auto-width} grow-type) 100000 width)
|
|
||||||
:height (if (#{:auto-height :auto-width} grow-type) 100000 height)}
|
|
||||||
|
|
||||||
[:& text-shape-edit-html {:shape shape :key (str id)}]]]]))
|
|
||||||
|
|
|
@ -110,6 +110,8 @@
|
||||||
;; Only when we have all the selected shapes in one frame
|
;; Only when we have all the selected shapes in one frame
|
||||||
selected-frame (when (= (count selected-frames) 1) (get base-objects (first selected-frames)))
|
selected-frame (when (= (count selected-frames) 1) (get base-objects (first selected-frames)))
|
||||||
|
|
||||||
|
editing-shape (when edition (get base-objects edition))
|
||||||
|
|
||||||
create-comment? (= :comments drawing-tool)
|
create-comment? (= :comments drawing-tool)
|
||||||
drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode])))
|
drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode])))
|
||||||
(and (some? drawing-obj) (= :path (:type drawing-obj))))
|
(and (some? drawing-obj) (= :path (:type drawing-obj))))
|
||||||
|
@ -159,6 +161,8 @@
|
||||||
show-artboard-names? (contains? layout :display-artboard-names)
|
show-artboard-names? (contains? layout :display-artboard-names)
|
||||||
show-rules? (and (contains? layout :rules) (not (contains? layout :hide-ui)))
|
show-rules? (and (contains? layout :rules) (not (contains? layout :hide-ui)))
|
||||||
|
|
||||||
|
show-text-editor? (and editing-shape (= :text (:type editing-shape)))
|
||||||
|
|
||||||
disabled-guides? (or drawing-tool transform)]
|
disabled-guides? (or drawing-tool transform)]
|
||||||
|
|
||||||
(hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?)
|
(hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?)
|
||||||
|
@ -176,6 +180,10 @@
|
||||||
[:& wtr/frame-renderer {:objects base-objects
|
[:& wtr/frame-renderer {:objects base-objects
|
||||||
:background background}]
|
:background background}]
|
||||||
|
|
||||||
|
(when show-text-editor?
|
||||||
|
[:& editor/text-editor-viewport {:shape editing-shape
|
||||||
|
:viewport-ref viewport-ref
|
||||||
|
:zoom zoom}])
|
||||||
(when show-comments?
|
(when show-comments?
|
||||||
[:& comments/comments-layer {:vbox vbox
|
[:& comments/comments-layer {:vbox vbox
|
||||||
:vport vport
|
:vport vport
|
||||||
|
@ -268,9 +276,6 @@
|
||||||
:hover-shape @hover
|
:hover-shape @hover
|
||||||
:zoom zoom}])
|
:zoom zoom}])
|
||||||
|
|
||||||
(when text-editing?
|
|
||||||
[:& editor/text-shape-edit {:shape (get base-objects edition)}])
|
|
||||||
|
|
||||||
[:& widgets/frame-titles
|
[:& widgets/frame-titles
|
||||||
{:objects objects-modified
|
{:objects objects-modified
|
||||||
:selected selected
|
:selected selected
|
||||||
|
|
Loading…
Add table
Reference in a new issue