mirror of
https://github.com/penpot/penpot.git
synced 2025-05-21 10:16:11 +02:00
✨ Improved performance for auto-width/auto-height texts
This commit is contained in:
parent
29b1b4dbc9
commit
32350bcf87
12 changed files with 112 additions and 53 deletions
|
@ -11,7 +11,7 @@
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.common.types.shape.layout :as ctl]))
|
[app.common.types.shape.layout :as ctl]))
|
||||||
|
|
||||||
(defn- child-layout-bound-points
|
(defn child-layout-bound-points
|
||||||
"Returns the bounds of the children as points"
|
"Returns the bounds of the children as points"
|
||||||
[parent child parent-bounds child-bounds]
|
[parent child parent-bounds child-bounds]
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
[app.common.geom.shapes.constraints :as gct]
|
[app.common.geom.shapes.constraints :as gct]
|
||||||
[app.common.geom.shapes.flex-layout :as gcl]
|
[app.common.geom.shapes.flex-layout :as gcl]
|
||||||
[app.common.geom.shapes.pixel-precision :as gpp]
|
[app.common.geom.shapes.pixel-precision :as gpp]
|
||||||
[app.common.geom.shapes.points :as cpo]
|
|
||||||
[app.common.geom.shapes.points :as gpo]
|
[app.common.geom.shapes.points :as gpo]
|
||||||
[app.common.geom.shapes.transforms :as gtr]
|
[app.common.geom.shapes.transforms :as gtr]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
|
@ -158,7 +157,7 @@
|
||||||
children-bounds
|
children-bounds
|
||||||
(->> children
|
(->> children
|
||||||
(mapv #(get-group-bounds objects bounds modif-tree %)))]
|
(mapv #(get-group-bounds objects bounds modif-tree %)))]
|
||||||
(cpo/merge-parent-coords-bounds children-bounds current-bounds))
|
(gpo/merge-parent-coords-bounds children-bounds current-bounds))
|
||||||
|
|
||||||
(cph/mask-shape? shape)
|
(cph/mask-shape? shape)
|
||||||
(get-group-bounds objects bounds modif-tree (-> children first))
|
(get-group-bounds objects bounds modif-tree (-> children first))
|
||||||
|
|
|
@ -384,6 +384,17 @@
|
||||||
(-> (empty)
|
(-> (empty)
|
||||||
(scale-content value)))
|
(scale-content value)))
|
||||||
|
|
||||||
|
(defn change-size
|
||||||
|
[{:keys [selrect points transform transform-inverse] :as shape} width height]
|
||||||
|
(let [old-width (-> selrect :width)
|
||||||
|
old-height (-> selrect :height)
|
||||||
|
width (or width old-width)
|
||||||
|
height (or height old-height)
|
||||||
|
origin (first points)
|
||||||
|
scalex (/ width old-width)
|
||||||
|
scaley (/ height old-height)]
|
||||||
|
(resize-modifiers (gpt/point scalex scaley) origin transform transform-inverse)))
|
||||||
|
|
||||||
(defn change-dimensions-modifiers
|
(defn change-dimensions-modifiers
|
||||||
[{:keys [transform transform-inverse] :as shape} attr value]
|
[{:keys [transform transform-inverse] :as shape} attr value]
|
||||||
(us/assert map? shape)
|
(us/assert map? shape)
|
||||||
|
|
|
@ -375,7 +375,10 @@
|
||||||
"Initializes the selrect and points for a shape."
|
"Initializes the selrect and points for a shape."
|
||||||
[shape]
|
[shape]
|
||||||
(let [selrect (gsh/rect->selrect shape)
|
(let [selrect (gsh/rect->selrect shape)
|
||||||
points (gsh/rect->points shape)]
|
points (gsh/rect->points shape)
|
||||||
|
points (cond-> points
|
||||||
|
(:transform shape)
|
||||||
|
(gsh/transform-points (gsh/center-points points) (:transform shape)))]
|
||||||
(-> shape
|
(-> shape
|
||||||
(assoc :selrect selrect
|
(assoc :selrect selrect
|
||||||
:points points))))
|
:points points))))
|
||||||
|
|
|
@ -14,13 +14,12 @@
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.common.text :as txt]
|
[app.common.text :as txt]
|
||||||
[app.common.types.modifiers :as ctm]
|
[app.common.types.modifiers :as ctm]
|
||||||
[app.common.types.shape :as cts]
|
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.main.data.workspace.changes :as dch]
|
[app.main.data.workspace.changes :as dch]
|
||||||
[app.main.data.workspace.common :as dwc]
|
[app.main.data.workspace.common :as dwc]
|
||||||
|
[app.main.data.workspace.modifiers :as dwm]
|
||||||
[app.main.data.workspace.selection :as dws]
|
[app.main.data.workspace.selection :as dws]
|
||||||
[app.main.data.workspace.shapes :as dwsh]
|
[app.main.data.workspace.shapes :as dwsh]
|
||||||
[app.main.data.workspace.shapes-update-layout :as dwul]
|
|
||||||
[app.main.data.workspace.state-helpers :as wsh]
|
[app.main.data.workspace.state-helpers :as wsh]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
[app.util.router :as rt]
|
[app.util.router :as rt]
|
||||||
|
@ -74,11 +73,14 @@
|
||||||
(when (and (not= content (:content shape))
|
(when (and (not= content (:content shape))
|
||||||
(some? (:current-page-id state)))
|
(some? (:current-page-id state)))
|
||||||
(rx/of
|
(rx/of
|
||||||
(dch/update-shapes [id] (fn [shape]
|
(dch/update-shapes
|
||||||
(-> shape
|
[id]
|
||||||
(assoc :content content)
|
(fn [shape]
|
||||||
(merge modifiers)
|
(let [{:keys [width height]} modifiers]
|
||||||
(cts/setup-rect-selrect))))
|
(-> shape
|
||||||
|
(assoc :content content)
|
||||||
|
(cond-> (or (some? width) (some? height))
|
||||||
|
(gsh/transform-shape (ctm/change-size shape width height)))))))
|
||||||
(dwu/commit-undo-transaction (:id shape))))))
|
(dwu/commit-undo-transaction (:id shape))))))
|
||||||
|
|
||||||
(when (some? id)
|
(when (some? id)
|
||||||
|
@ -323,20 +325,25 @@
|
||||||
(let [shape (wsh/lookup-shape state id)]
|
(let [shape (wsh/lookup-shape state id)]
|
||||||
(letfn [(update-fn [shape]
|
(letfn [(update-fn [shape]
|
||||||
(let [{:keys [selrect grow-type]} shape
|
(let [{:keys [selrect grow-type]} shape
|
||||||
{shape-width :width shape-height :height} selrect]
|
{shape-width :width shape-height :height} selrect
|
||||||
(cond-> shape
|
|
||||||
(and (not-changed? shape-width new-width) (= grow-type :auto-width))
|
|
||||||
(gsh/transform-shape (ctm/change-dimensions-modifiers shape :width new-width))
|
|
||||||
|
|
||||||
(and (not-changed? shape-height new-height)
|
shape
|
||||||
(or (= grow-type :auto-height) (= grow-type :auto-width)))
|
(cond-> shape
|
||||||
(gsh/transform-shape (ctm/change-dimensions-modifiers shape :height new-height)))))]
|
(and (not-changed? shape-width new-width) (= grow-type :auto-width))
|
||||||
|
(gsh/transform-shape (ctm/change-dimensions-modifiers shape :width new-width)))
|
||||||
|
|
||||||
|
shape
|
||||||
|
(cond-> shape
|
||||||
|
(and (not-changed? shape-height new-height)
|
||||||
|
(or (= grow-type :auto-height) (= grow-type :auto-width)))
|
||||||
|
(gsh/transform-shape (ctm/change-dimensions-modifiers shape :height new-height)))]
|
||||||
|
|
||||||
|
shape))]
|
||||||
|
|
||||||
(when (or (and (not-changed? (:width shape) new-width) (= (:grow-type shape) :auto-width))
|
(when (or (and (not-changed? (:width shape) new-width) (= (:grow-type shape) :auto-width))
|
||||||
(and (not-changed? (:height shape) new-height)
|
(and (not-changed? (:height shape) new-height)
|
||||||
(or (= (:grow-type shape) :auto-height) (= (:grow-type shape) :auto-width))))
|
(or (= (:grow-type shape) :auto-height) (= (:grow-type shape) :auto-width))))
|
||||||
(rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false})
|
(rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false}))))))))
|
||||||
(dwul/update-layout-positions [id]))))))))
|
|
||||||
|
|
||||||
(defn save-font
|
(defn save-font
|
||||||
[data]
|
[data]
|
||||||
|
@ -385,7 +392,9 @@
|
||||||
(not (mth/close? (:width props) (:width shape))))
|
(not (mth/close? (:width props) (:width shape))))
|
||||||
(and (some? (:height props))
|
(and (some? (:height props))
|
||||||
(not (mth/close? (:height props) (:height shape)))))
|
(not (mth/close? (:height props) (:height shape)))))
|
||||||
(rx/of (dwul/update-layout-positions [id])))))))
|
|
||||||
|
(let [modif-tree (dwm/create-modif-tree [id] (ctm/reflow-modifiers))]
|
||||||
|
(rx/of (dwm/set-modifiers modif-tree))))))))
|
||||||
|
|
||||||
(defn clean-text-modifier
|
(defn clean-text-modifier
|
||||||
[id]
|
[id]
|
||||||
|
@ -401,7 +410,11 @@
|
||||||
(ptk/reify ::remove-text-modifier
|
(ptk/reify ::remove-text-modifier
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(d/dissoc-in state [:workspace-text-modifier id]))))
|
(d/dissoc-in state [:workspace-text-modifier id]))
|
||||||
|
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ _ _]
|
||||||
|
(rx/of (dwm/apply-modifiers)))))
|
||||||
|
|
||||||
(defn commit-position-data
|
(defn commit-position-data
|
||||||
[]
|
[]
|
||||||
|
|
|
@ -257,7 +257,9 @@
|
||||||
(mf/defc text-editor-svg
|
(mf/defc text-editor-svg
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (obj/get props "shape")
|
(let [shape (obj/get props "shape")
|
||||||
|
modifiers (obj/get props "modifiers")
|
||||||
|
modifiers (get-in modifiers [(:id shape) :modifiers])
|
||||||
|
|
||||||
clip-id
|
clip-id
|
||||||
(dm/str "text-edition-clip" (:id shape))
|
(dm/str "text-edition-clip" (:id shape))
|
||||||
|
@ -270,7 +272,10 @@
|
||||||
|
|
||||||
shape (cond-> shape
|
shape (cond-> shape
|
||||||
(some? text-modifier)
|
(some? text-modifier)
|
||||||
(dwt/apply-text-modifier text-modifier))
|
(dwt/apply-text-modifier text-modifier)
|
||||||
|
|
||||||
|
(some? modifiers)
|
||||||
|
(gsh/transform-shape modifiers))
|
||||||
|
|
||||||
bounding-box (gsht/position-data-selrect shape)
|
bounding-box (gsht/position-data-selrect shape)
|
||||||
|
|
||||||
|
|
|
@ -12,14 +12,19 @@
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(mf/defc text-edition-outline
|
(mf/defc text-edition-outline
|
||||||
[{:keys [shape zoom]}]
|
[{:keys [shape zoom modifiers]}]
|
||||||
(let [text-modifier-ref
|
(let [modifiers (get-in modifiers [(:id shape) :modifiers])
|
||||||
|
|
||||||
|
text-modifier-ref
|
||||||
(mf/use-memo (mf/deps (:id shape)) #(refs/workspace-text-modifier-by-id (:id shape)))
|
(mf/use-memo (mf/deps (:id shape)) #(refs/workspace-text-modifier-by-id (:id shape)))
|
||||||
|
|
||||||
text-modifier
|
text-modifier
|
||||||
(mf/deref text-modifier-ref)
|
(mf/deref text-modifier-ref)
|
||||||
|
|
||||||
shape (cond-> shape
|
shape (cond-> shape
|
||||||
|
(some? modifiers)
|
||||||
|
(gsh/transform-shape modifiers)
|
||||||
|
|
||||||
(some? text-modifier)
|
(some? text-modifier)
|
||||||
(dwt/apply-text-modifier text-modifier))
|
(dwt/apply-text-modifier text-modifier))
|
||||||
|
|
||||||
|
|
|
@ -36,13 +36,11 @@
|
||||||
(dissoc :position-data)))
|
(dissoc :position-data)))
|
||||||
|
|
||||||
(defn fix-position [shape modifier]
|
(defn fix-position [shape modifier]
|
||||||
(let [shape' (-> shape
|
(let [shape' (gsh/transform-shape shape modifier)
|
||||||
(assoc :grow-type :fixed)
|
;; We need to remove the movement because the dynamic modifiers will have move it
|
||||||
(gsh/transform-shape modifier))
|
|
||||||
|
|
||||||
deltav (gpt/to-vec (gpt/point (:selrect shape'))
|
deltav (gpt/to-vec (gpt/point (:selrect shape'))
|
||||||
(gpt/point (:selrect shape)))]
|
(gpt/point (:selrect shape)))]
|
||||||
(gsh/transform-shape shape' (ctm/move-modifiers deltav))))
|
(gsh/transform-shape shape (ctm/move modifier deltav))))
|
||||||
|
|
||||||
(defn process-shape [modifiers {:keys [id] :as shape}]
|
(defn process-shape [modifiers {:keys [id] :as shape}]
|
||||||
(let [modifier (dm/get-in modifiers [id :modifiers])]
|
(let [modifier (dm/get-in modifiers [id :modifiers])]
|
||||||
|
|
|
@ -204,9 +204,9 @@
|
||||||
[:div.viewport-overlays
|
[:div.viewport-overlays
|
||||||
;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap
|
;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap
|
||||||
;; inside a foreign object "dummy" so this awkward behaviour is take into account
|
;; inside a foreign object "dummy" so this awkward behaviour is take into account
|
||||||
[:svg {:style {:top 0 :left 0 :position "fixed" :width "100%" :height "100%" :opacity 0}}
|
[:svg {:style {:top 0 :left 0 :position "fixed" :width "100%" :height "100%" :opacity (when-not (debug? :html-text) 0)}}
|
||||||
[:foreignObject {:x 0 :y 0 :width "100%" :height "100%"}
|
[:foreignObject {:x 0 :y 0 :width "100%" :height "100%"}
|
||||||
[:div {:style {:pointer-events "none"}}
|
[:div {:style {:pointer-events (when-not (debug? :html-text) "none")}}
|
||||||
[:& stvh/viewport-texts
|
[:& stvh/viewport-texts
|
||||||
{:key (dm/str "texts-" page-id)
|
{:key (dm/str "texts-" page-id)
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
|
@ -289,7 +289,8 @@
|
||||||
|
|
||||||
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
|
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
|
||||||
(when show-text-editor?
|
(when show-text-editor?
|
||||||
[:& editor/text-editor-svg {:shape editing-shape}])
|
[:& editor/text-editor-svg {:shape editing-shape
|
||||||
|
:modifiers modifiers}])
|
||||||
|
|
||||||
(when show-frame-outline?
|
(when show-frame-outline?
|
||||||
[:& outline/shape-outlines
|
[:& outline/shape-outlines
|
||||||
|
@ -298,7 +299,8 @@
|
||||||
(filter #(cph/frame-shape? (get base-objects %)))
|
(filter #(cph/frame-shape? (get base-objects %)))
|
||||||
(remove selected)
|
(remove selected)
|
||||||
(first))}
|
(first))}
|
||||||
:zoom zoom}])
|
:zoom zoom
|
||||||
|
:modifiers modifiers}])
|
||||||
|
|
||||||
(when show-outlines?
|
(when show-outlines?
|
||||||
[:& outline/shape-outlines
|
[:& outline/shape-outlines
|
||||||
|
@ -307,7 +309,8 @@
|
||||||
:hover #{(:id @hover) @frame-hover}
|
:hover #{(:id @hover) @frame-hover}
|
||||||
:highlighted highlighted
|
:highlighted highlighted
|
||||||
:edition edition
|
:edition edition
|
||||||
:zoom zoom}])
|
:zoom zoom
|
||||||
|
:modifiers modifiers}])
|
||||||
|
|
||||||
(when show-selection-handlers?
|
(when show-selection-handlers?
|
||||||
[:& selection/selection-area
|
[:& selection/selection-area
|
||||||
|
@ -321,7 +324,8 @@
|
||||||
(when show-text-editor?
|
(when show-text-editor?
|
||||||
[:& text-edition-outline
|
[:& text-edition-outline
|
||||||
{:shape (get base-objects edition)
|
{:shape (get base-objects edition)
|
||||||
:zoom zoom}])
|
:zoom zoom
|
||||||
|
:modifiers modifiers}])
|
||||||
|
|
||||||
(when show-measures?
|
(when show-measures?
|
||||||
[:& msr/measurement
|
[:& msr/measurement
|
||||||
|
|
|
@ -130,12 +130,15 @@
|
||||||
rect (gsh/center->rect point (/ 5 zoom) (/ 5 zoom))]
|
rect (gsh/center->rect point (/ 5 zoom) (/ 5 zoom))]
|
||||||
(if (mf/ref-val hover-disabled-ref)
|
(if (mf/ref-val hover-disabled-ref)
|
||||||
(rx/of nil)
|
(rx/of nil)
|
||||||
(uw/ask-buffered!
|
(->> (uw/ask-buffered!
|
||||||
{:cmd :selection/query
|
{:cmd :selection/query
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:rect rect
|
:rect rect
|
||||||
:include-frames? true
|
:include-frames? true
|
||||||
:clip-children? (not mod?)})))))
|
:clip-children? (not mod?)})
|
||||||
|
;; When the ask-buffered is canceled returns null. We filter them
|
||||||
|
;; to improve the behavior
|
||||||
|
(rx/filter some?))))))
|
||||||
|
|
||||||
over-shapes-stream
|
over-shapes-stream
|
||||||
(mf/use-memo
|
(mf/use-memo
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.main.ui.hooks :as hooks]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.path.format :as upf]
|
[app.util.path.format :as upf]
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
|
@ -18,10 +19,12 @@
|
||||||
(mf/defc outline
|
(mf/defc outline
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
(let [shape (obj/get props "shape")
|
(let [shape (obj/get props "shape")
|
||||||
zoom (obj/get props "zoom" 1)
|
zoom (obj/get props "zoom" 1)
|
||||||
|
color (obj/get props "color")
|
||||||
|
modifier (obj/get props "modifier")
|
||||||
|
|
||||||
color (unchecked-get props "color")
|
shape (gsh/transform-shape shape (:modifiers modifier))
|
||||||
transform (gsh/transform-str shape)
|
transform (gsh/transform-str shape)
|
||||||
path? (= :path (:type shape))
|
path? (= :path (:type shape))
|
||||||
path-data
|
path-data
|
||||||
|
@ -64,17 +67,22 @@
|
||||||
|
|
||||||
(mf/defc shape-outlines-render
|
(mf/defc shape-outlines-render
|
||||||
{::mf/wrap-props false
|
{::mf/wrap-props false
|
||||||
::mf/wrap [#(mf/memo' % (mf/check-props ["shapes" "zoom"]))]}
|
::mf/wrap [#(mf/memo' % (mf/check-props ["shapes" "zoom" "modifiers"]))]}
|
||||||
[props]
|
[props]
|
||||||
|
|
||||||
(let [shapes (obj/get props "shapes")
|
(let [shapes (obj/get props "shapes")
|
||||||
zoom (obj/get props "zoom")
|
zoom (obj/get props "zoom")
|
||||||
|
modifiers (obj/get props "modifiers")
|
||||||
color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes))))
|
color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes))))
|
||||||
"var(--color-primary)" "var(--color-component-highlight)")]
|
"var(--color-primary)" "var(--color-component-highlight)")]
|
||||||
|
|
||||||
(for [shape shapes]
|
(for [shape shapes]
|
||||||
[:& outline {:key (str "outline-" (:id shape))
|
(let [modifier (get modifiers (:id shape))]
|
||||||
:shape shape
|
[:& outline {:key (str "outline-" (:id shape))
|
||||||
:zoom zoom
|
:shape shape
|
||||||
:color color}])))
|
:modifier modifier
|
||||||
|
:zoom zoom
|
||||||
|
:color color}]))))
|
||||||
|
|
||||||
(defn- show-outline?
|
(defn- show-outline?
|
||||||
[shape]
|
[shape]
|
||||||
|
@ -91,6 +99,7 @@
|
||||||
objects (obj/get props "objects")
|
objects (obj/get props "objects")
|
||||||
edition (obj/get props "edition")
|
edition (obj/get props "edition")
|
||||||
zoom (obj/get props "zoom")
|
zoom (obj/get props "zoom")
|
||||||
|
modifiers (obj/get props "modifiers")
|
||||||
|
|
||||||
lookup (d/getf objects)
|
lookup (d/getf objects)
|
||||||
edition? (fn [o] (= edition o))
|
edition? (fn [o] (= edition o))
|
||||||
|
@ -102,7 +111,13 @@
|
||||||
(set/union selected hover))
|
(set/union selected hover))
|
||||||
(into (comp (remove edition?)
|
(into (comp (remove edition?)
|
||||||
(keep lookup))
|
(keep lookup))
|
||||||
highlighted))]
|
highlighted))
|
||||||
|
|
||||||
|
modifiers (select-keys modifiers (map :id shapes))
|
||||||
|
modifiers (hooks/use-equal-memo modifiers)
|
||||||
|
shapes (hooks/use-equal-memo shapes)]
|
||||||
|
|
||||||
[:g.outlines
|
[:g.outlines
|
||||||
[:& shape-outlines-render {:shapes shapes :zoom zoom}]]))
|
[:& shape-outlines-render {:shapes shapes
|
||||||
|
:zoom zoom
|
||||||
|
:modifiers modifiers}]]))
|
||||||
|
|
|
@ -83,6 +83,9 @@
|
||||||
|
|
||||||
;; Show the bounds relative to the parent
|
;; Show the bounds relative to the parent
|
||||||
:parent-bounds
|
:parent-bounds
|
||||||
|
|
||||||
|
;; Show html text
|
||||||
|
:html-text
|
||||||
})
|
})
|
||||||
|
|
||||||
;; These events are excluded when we activate the :events flag
|
;; These events are excluded when we activate the :events flag
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue