mirror of
https://github.com/penpot/penpot.git
synced 2025-05-10 20:16:37 +02:00
✨ Copy/paste properties an CSS
This commit is contained in:
parent
80d6968156
commit
714a274789
11 changed files with 1485 additions and 23 deletions
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
- New gradients UI with multi-stop support.
|
- New gradients UI with multi-stop support.
|
||||||
- Shareable link pointing to an specific board.
|
- Shareable link pointing to an specific board.
|
||||||
|
- Copy styles in CSS
|
||||||
|
- Copy/paste shape styles (fills, strokes, shadows, etc..)
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#?(:clj [app.common.fressian :as fres])
|
#?(:clj [app.common.fressian :as fres])
|
||||||
[app.common.colors :as clr]
|
[app.common.colors :as clr]
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.files.helpers :as cfh]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.proportions :as gpr]
|
[app.common.geom.proportions :as gpr]
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
[app.common.record :as cr]
|
[app.common.record :as cr]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.schema.generators :as sg]
|
[app.common.schema.generators :as sg]
|
||||||
|
[app.common.text :as txt]
|
||||||
[app.common.transit :as t]
|
[app.common.transit :as t]
|
||||||
[app.common.types.color :as ctc]
|
[app.common.types.color :as ctc]
|
||||||
[app.common.types.grid :as ctg]
|
[app.common.types.grid :as ctg]
|
||||||
|
@ -570,3 +572,136 @@
|
||||||
:class Shape
|
:class Shape
|
||||||
:wfn fres/write-map-like
|
:wfn fres/write-map-like
|
||||||
:rfn (comp map->Shape fres/read-map-like)}))
|
:rfn (comp map->Shape fres/read-map-like)}))
|
||||||
|
|
||||||
|
;; --- SHAPE COPY/PASTE PROPS
|
||||||
|
|
||||||
|
;; Copy/paste properties:
|
||||||
|
;; - Fill
|
||||||
|
;; - Stroke
|
||||||
|
;; - Opacity
|
||||||
|
;; - Layout (Grid & Flex)
|
||||||
|
;; - Flex element
|
||||||
|
;; - Flex board
|
||||||
|
;; - Text properties
|
||||||
|
;; - Contraints
|
||||||
|
;; - Shadow
|
||||||
|
;; - Blur
|
||||||
|
;; - Border radius
|
||||||
|
(def ^:private basic-extract-props
|
||||||
|
[:fills
|
||||||
|
:strokes
|
||||||
|
:opacity
|
||||||
|
|
||||||
|
;; Layout Item
|
||||||
|
:layout-item-margin
|
||||||
|
:layout-item-margin-type
|
||||||
|
:layout-item-h-sizing
|
||||||
|
:layout-item-v-sizing
|
||||||
|
:layout-item-max-h
|
||||||
|
:layout-item-min-h
|
||||||
|
:layout-item-max-w
|
||||||
|
:layout-item-min-w
|
||||||
|
:layout-item-absolute
|
||||||
|
:layout-item-z-index
|
||||||
|
|
||||||
|
;; Constraints
|
||||||
|
:constraints-h
|
||||||
|
:constraints-v
|
||||||
|
|
||||||
|
:shadow
|
||||||
|
:blur
|
||||||
|
|
||||||
|
;; Radius
|
||||||
|
:r1
|
||||||
|
:r2
|
||||||
|
:r3
|
||||||
|
:r4])
|
||||||
|
|
||||||
|
(def ^:private layout-extract-props
|
||||||
|
[:layout
|
||||||
|
:layout-flex-dir
|
||||||
|
:layout-gap-type
|
||||||
|
:layout-gap
|
||||||
|
:layout-wrap-type
|
||||||
|
:layout-align-items
|
||||||
|
:layout-align-content
|
||||||
|
:layout-justify-items
|
||||||
|
:layout-justify-content
|
||||||
|
:layout-padding-type
|
||||||
|
:layout-padding
|
||||||
|
:layout-grid-dir
|
||||||
|
:layout-grid-rows
|
||||||
|
:layout-grid-columns
|
||||||
|
:layout-grid-cells])
|
||||||
|
|
||||||
|
(defn extract-props
|
||||||
|
"Retrieves an object with the 'pasteable' properties for a shape."
|
||||||
|
[shape]
|
||||||
|
(letfn [(assoc-props
|
||||||
|
[props node attrs]
|
||||||
|
(->> attrs
|
||||||
|
(reduce
|
||||||
|
(fn [props attr]
|
||||||
|
(cond-> props
|
||||||
|
(and (not (contains? props attr))
|
||||||
|
(some? (get node attr)))
|
||||||
|
(assoc attr (get node attr))))
|
||||||
|
props)))
|
||||||
|
|
||||||
|
(extract-text-props
|
||||||
|
[props shape]
|
||||||
|
(->> (txt/node-seq (:content shape))
|
||||||
|
(reduce
|
||||||
|
(fn [result node]
|
||||||
|
(cond-> result
|
||||||
|
(txt/is-root-node? node)
|
||||||
|
(assoc-props node txt/root-attrs)
|
||||||
|
|
||||||
|
(txt/is-paragraph-node? node)
|
||||||
|
(assoc-props node txt/paragraph-attrs)
|
||||||
|
|
||||||
|
(txt/is-text-node? node)
|
||||||
|
(assoc-props node txt/text-node-attrs)))
|
||||||
|
props)))
|
||||||
|
|
||||||
|
(extract-layout-props
|
||||||
|
[props shape]
|
||||||
|
(d/patch-object props (select-keys shape layout-extract-props)))]
|
||||||
|
|
||||||
|
(-> shape
|
||||||
|
(select-keys basic-extract-props)
|
||||||
|
(cond-> (cfh/text-shape? shape) (extract-text-props shape))
|
||||||
|
(cond-> (ctsl/any-layout? shape) (extract-layout-props shape)))))
|
||||||
|
|
||||||
|
(defn patch-props
|
||||||
|
"Given the object of `extract-props` applies it to a shape. Adapt the shape if necesary"
|
||||||
|
[shape props objects]
|
||||||
|
|
||||||
|
(letfn [(patch-text-props [shape props]
|
||||||
|
(-> shape
|
||||||
|
(update
|
||||||
|
:content
|
||||||
|
(fn [content]
|
||||||
|
(->> content
|
||||||
|
(txt/transform-nodes
|
||||||
|
(fn [node]
|
||||||
|
(cond-> node
|
||||||
|
(txt/is-root-node? node)
|
||||||
|
(d/patch-object (select-keys props txt/root-attrs))
|
||||||
|
|
||||||
|
(txt/is-paragraph-node? node)
|
||||||
|
(d/patch-object (select-keys props txt/paragraph-attrs))
|
||||||
|
|
||||||
|
(txt/is-text-node? node)
|
||||||
|
(d/patch-object (select-keys props txt/text-node-attrs))))))))))
|
||||||
|
|
||||||
|
(patch-layout-props [shape props]
|
||||||
|
(let [shape (d/patch-object shape (select-keys props layout-extract-props))]
|
||||||
|
(cond-> shape
|
||||||
|
(ctsl/grid-layout? shape)
|
||||||
|
(ctsl/assign-cells objects))))]
|
||||||
|
|
||||||
|
(-> shape
|
||||||
|
(d/patch-object (select-keys props basic-extract-props))
|
||||||
|
(cond-> (cfh/text-shape? shape) (patch-text-props props))
|
||||||
|
(cond-> (cfh/frame-shape? shape) (patch-layout-props props)))))
|
||||||
|
|
|
@ -0,0 +1,920 @@
|
||||||
|
{
|
||||||
|
"~:id": "~u870f9f10-87b5-8137-8005-93487d148645",
|
||||||
|
"~:file-id": "~u870f9f10-87b5-8137-8005-934804124660",
|
||||||
|
"~:created-at": "~m1736778551374",
|
||||||
|
"~:data": {
|
||||||
|
"~:options": {},
|
||||||
|
"~:objects": {
|
||||||
|
"~u00000000-0000-0000-0000-000000000000": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": 0,
|
||||||
|
"~:hide-fill-on-export": false,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:name": "Root Frame",
|
||||||
|
"~:width": 0.01,
|
||||||
|
"~:type": "~:frame",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 0.0,
|
||||||
|
"~:y": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 0.01,
|
||||||
|
"~:y": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 0.01,
|
||||||
|
"~:y": 0.01
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 0.0,
|
||||||
|
"~:y": 0.01
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:r2": 0,
|
||||||
|
"~:proportion-lock": false,
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:r3": 0,
|
||||||
|
"~:r1": 0,
|
||||||
|
"~:id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:strokes": [],
|
||||||
|
"~:x": 0,
|
||||||
|
"~:proportion": 1.0,
|
||||||
|
"~:r4": 0,
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": 0,
|
||||||
|
"~:y": 0,
|
||||||
|
"~:width": 0.01,
|
||||||
|
"~:height": 0.01,
|
||||||
|
"~:x1": 0,
|
||||||
|
"~:y1": 0,
|
||||||
|
"~:x2": 0.01,
|
||||||
|
"~:y2": 0.01
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#FFFFFF",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 0.01,
|
||||||
|
"~:flip-y": null,
|
||||||
|
"~:shapes": [
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-934805578f93",
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-93484afe7510",
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-93484d68344f",
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-9348531e39f7",
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-934855b02e52",
|
||||||
|
"~ud4467b11-7129-80c3-8005-934871e790b5"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-934805578f93": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": 283,
|
||||||
|
"~:hide-fill-on-export": false,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:grow-type": "~:fixed",
|
||||||
|
"~:hide-in-viewer": false,
|
||||||
|
"~:name": "Board",
|
||||||
|
"~:width": 406,
|
||||||
|
"~:type": "~:frame",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 460,
|
||||||
|
"~:y": 283
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 866,
|
||||||
|
"~:y": 283
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 866,
|
||||||
|
"~:y": 766
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 460,
|
||||||
|
"~:y": 766
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:r2": 0,
|
||||||
|
"~:proportion-lock": false,
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:r3": 0,
|
||||||
|
"~:r1": 0,
|
||||||
|
"~:id": "~u0eef4dd0-b39b-807a-8005-934805578f93",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:strokes": [],
|
||||||
|
"~:x": 460,
|
||||||
|
"~:proportion": 1,
|
||||||
|
"~:r4": 0,
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": 460,
|
||||||
|
"~:y": 283,
|
||||||
|
"~:width": 406,
|
||||||
|
"~:height": 483,
|
||||||
|
"~:x1": 460,
|
||||||
|
"~:y1": 283,
|
||||||
|
"~:x2": 866,
|
||||||
|
"~:y2": 766
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#FFFFFF",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 483,
|
||||||
|
"~:flip-y": null,
|
||||||
|
"~:shapes": [
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-934808365fa2"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-934808365fa2": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": 339,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:grow-type": "~:fixed",
|
||||||
|
"~:hide-in-viewer": false,
|
||||||
|
"~:name": "Rectangle",
|
||||||
|
"~:width": 117,
|
||||||
|
"~:type": "~:rect",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 530,
|
||||||
|
"~:y": 339
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 647,
|
||||||
|
"~:y": 339
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 647,
|
||||||
|
"~:y": 453
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 530,
|
||||||
|
"~:y": 453
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:r2": 0,
|
||||||
|
"~:proportion-lock": false,
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:r3": 0,
|
||||||
|
"~:r1": 0,
|
||||||
|
"~:id": "~u0eef4dd0-b39b-807a-8005-934808365fa2",
|
||||||
|
"~:parent-id": "~u0eef4dd0-b39b-807a-8005-934805578f93",
|
||||||
|
"~:frame-id": "~u0eef4dd0-b39b-807a-8005-934805578f93",
|
||||||
|
"~:strokes": [
|
||||||
|
{
|
||||||
|
"~:stroke-alignment": "~:outer",
|
||||||
|
"~:stroke-style": "~:solid",
|
||||||
|
"~:stroke-color": "#000000",
|
||||||
|
"~:stroke-opacity": 1,
|
||||||
|
"~:stroke-width": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:x": 530,
|
||||||
|
"~:proportion": 1,
|
||||||
|
"~:r4": 0,
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": 530,
|
||||||
|
"~:y": 339,
|
||||||
|
"~:width": 117,
|
||||||
|
"~:height": 114,
|
||||||
|
"~:x1": 530,
|
||||||
|
"~:y1": 339,
|
||||||
|
"~:x2": 647,
|
||||||
|
"~:y2": 453
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color-gradient": {
|
||||||
|
"~:start-x": 0.5,
|
||||||
|
"~:start-y": 0,
|
||||||
|
"~:end-x": 0.5,
|
||||||
|
"~:end-y": 1,
|
||||||
|
"~:width": 1,
|
||||||
|
"~:type": "~:linear",
|
||||||
|
"~:stops": [
|
||||||
|
{
|
||||||
|
"~:color": "#ff0000",
|
||||||
|
"~:file-id": null,
|
||||||
|
"~:offset": 0,
|
||||||
|
"~:opacity": 1,
|
||||||
|
"~:id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:color": "#ff8600",
|
||||||
|
"~:file-id": null,
|
||||||
|
"~:offset": 0.17,
|
||||||
|
"~:opacity": 1,
|
||||||
|
"~:id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:color": "#f6ff00",
|
||||||
|
"~:file-id": null,
|
||||||
|
"~:offset": 0.33,
|
||||||
|
"~:opacity": 1,
|
||||||
|
"~:id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:color": "#24ff00",
|
||||||
|
"~:file-id": null,
|
||||||
|
"~:offset": 0.5,
|
||||||
|
"~:opacity": 1,
|
||||||
|
"~:id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:color": "#00ffe6",
|
||||||
|
"~:file-id": null,
|
||||||
|
"~:offset": 0.67,
|
||||||
|
"~:opacity": 1,
|
||||||
|
"~:id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:color": "#2500ff",
|
||||||
|
"~:file-id": null,
|
||||||
|
"~:offset": 0.83,
|
||||||
|
"~:opacity": 1,
|
||||||
|
"~:id": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:color": "#bf00ff",
|
||||||
|
"~:file-id": null,
|
||||||
|
"~:offset": 1,
|
||||||
|
"~:opacity": 1,
|
||||||
|
"~:id": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 114,
|
||||||
|
"~:flip-y": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-93484afe7510": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": 314,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:grow-type": "~:fixed",
|
||||||
|
"~:hide-in-viewer": false,
|
||||||
|
"~:name": "Ellipse",
|
||||||
|
"~:width": 103,
|
||||||
|
"~:type": "~:circle",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1151,
|
||||||
|
"~:y": 314
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1254,
|
||||||
|
"~:y": 314
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1254,
|
||||||
|
"~:y": 417
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1151,
|
||||||
|
"~:y": 417
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:proportion-lock": false,
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:id": "~u0eef4dd0-b39b-807a-8005-93484afe7510",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:strokes": [],
|
||||||
|
"~:x": 1151,
|
||||||
|
"~:proportion": 1,
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": 1151,
|
||||||
|
"~:y": 314,
|
||||||
|
"~:width": 103,
|
||||||
|
"~:height": 103,
|
||||||
|
"~:x1": 1151,
|
||||||
|
"~:y1": 314,
|
||||||
|
"~:x2": 1254,
|
||||||
|
"~:y2": 417
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#B1B2B5",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 103,
|
||||||
|
"~:flip-y": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-93484d68344f": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": null,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:grow-type": "~:fixed",
|
||||||
|
"~:content": [
|
||||||
|
{
|
||||||
|
"~:command": "~:move-to",
|
||||||
|
"~:params": {
|
||||||
|
"~:x": 1121,
|
||||||
|
"~:y": 554
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:command": "~:line-to",
|
||||||
|
"~:params": {
|
||||||
|
"~:x": 1229,
|
||||||
|
"~:y": 458
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:command": "~:curve-to",
|
||||||
|
"~:params": {
|
||||||
|
"~:x": 1303,
|
||||||
|
"~:y": 518,
|
||||||
|
"~:c1x": 1229,
|
||||||
|
"~:c1y": 458,
|
||||||
|
"~:c2x": 1320,
|
||||||
|
"~:c2y": 492
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:command": "~:curve-to",
|
||||||
|
"~:params": {
|
||||||
|
"~:x": 1219,
|
||||||
|
"~:y": 584,
|
||||||
|
"~:c1x": 1286,
|
||||||
|
"~:c1y": 544,
|
||||||
|
"~:c2x": 1258,
|
||||||
|
"~:c2y": 572
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~:command": "~:curve-to",
|
||||||
|
"~:params": {
|
||||||
|
"~:x": 1121,
|
||||||
|
"~:y": 554,
|
||||||
|
"~:c1x": 1180,
|
||||||
|
"~:c1y": 596,
|
||||||
|
"~:c2x": 1121,
|
||||||
|
"~:c2y": 554
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:name": "Path",
|
||||||
|
"~:width": null,
|
||||||
|
"~:type": "~:path",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1121,
|
||||||
|
"~:y": 458
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1305.1163606979621,
|
||||||
|
"~:y": 458
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1305.1163606979621,
|
||||||
|
"~:y": 586.15625
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1121,
|
||||||
|
"~:y": 586.15625
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:proportion-lock": false,
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:id": "~u0eef4dd0-b39b-807a-8005-93484d68344f",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:strokes": [
|
||||||
|
{
|
||||||
|
"~:stroke-style": "~:solid",
|
||||||
|
"~:stroke-alignment": "~:inner",
|
||||||
|
"~:stroke-width": 2,
|
||||||
|
"~:stroke-color": "#000000",
|
||||||
|
"~:stroke-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:x": null,
|
||||||
|
"~:proportion": 1,
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": 1121,
|
||||||
|
"~:y": 458,
|
||||||
|
"~:width": 184.11636069796214,
|
||||||
|
"~:height": 128.15625,
|
||||||
|
"~:x1": 1121,
|
||||||
|
"~:y1": 458,
|
||||||
|
"~:x2": 1305.1163606979621,
|
||||||
|
"~:y2": 586.15625
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:fills": [],
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": null,
|
||||||
|
"~:flip-y": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-9348531e39f7": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": 645,
|
||||||
|
"~:hide-fill-on-export": false,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:grow-type": "~:fixed",
|
||||||
|
"~:hide-in-viewer": false,
|
||||||
|
"~:name": "Board",
|
||||||
|
"~:width": 140,
|
||||||
|
"~:type": "~:frame",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1150,
|
||||||
|
"~:y": 645
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1290,
|
||||||
|
"~:y": 645
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1290,
|
||||||
|
"~:y": 766
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1150,
|
||||||
|
"~:y": 766
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:r2": 0,
|
||||||
|
"~:proportion-lock": false,
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:r3": 0,
|
||||||
|
"~:r1": 0,
|
||||||
|
"~:id": "~u0eef4dd0-b39b-807a-8005-9348531e39f7",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:strokes": [],
|
||||||
|
"~:x": 1150,
|
||||||
|
"~:proportion": 1,
|
||||||
|
"~:r4": 0,
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": 1150,
|
||||||
|
"~:y": 645,
|
||||||
|
"~:width": 140,
|
||||||
|
"~:height": 121,
|
||||||
|
"~:x1": 1150,
|
||||||
|
"~:y1": 645,
|
||||||
|
"~:x2": 1290,
|
||||||
|
"~:y2": 766
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#FFFFFF",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 121,
|
||||||
|
"~:flip-y": null,
|
||||||
|
"~:shapes": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~u0eef4dd0-b39b-807a-8005-934855b02e52": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": 188,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:grow-type": "~:fixed",
|
||||||
|
"~:hide-in-viewer": false,
|
||||||
|
"~:name": "Rectangle",
|
||||||
|
"~:width": 100,
|
||||||
|
"~:type": "~:rect",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1154,
|
||||||
|
"~:y": 188
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1254,
|
||||||
|
"~:y": 188
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1254,
|
||||||
|
"~:y": 283
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1154,
|
||||||
|
"~:y": 283
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:r2": 0,
|
||||||
|
"~:proportion-lock": false,
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:r3": 0,
|
||||||
|
"~:r1": 0,
|
||||||
|
"~:id": "~u0eef4dd0-b39b-807a-8005-934855b02e52",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:strokes": [],
|
||||||
|
"~:x": 1154,
|
||||||
|
"~:proportion": 1,
|
||||||
|
"~:r4": 0,
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": 1154,
|
||||||
|
"~:y": 188,
|
||||||
|
"~:width": 100,
|
||||||
|
"~:height": 95,
|
||||||
|
"~:x1": 1154,
|
||||||
|
"~:y1": 188,
|
||||||
|
"~:x2": 1254,
|
||||||
|
"~:y2": 283
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#B1B2B5",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 95,
|
||||||
|
"~:flip-y": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~ud4467b11-7129-80c3-8005-934871e790b5": {
|
||||||
|
"~#shape": {
|
||||||
|
"~:y": 856,
|
||||||
|
"~:transform": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:rotation": 0,
|
||||||
|
"~:grow-type": "~:auto-width",
|
||||||
|
"~:content": {
|
||||||
|
"~:type": "root",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:type": "paragraph-set",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:line-height": "1.2",
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:children": [
|
||||||
|
{
|
||||||
|
"~:line-height": "1.2",
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:typography-ref-id": null,
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:text-align": "left",
|
||||||
|
"~:font-id": "sourcesanspro",
|
||||||
|
"~:font-size": "44",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:typography-ref-file": null,
|
||||||
|
"~:text-direction": "ltr",
|
||||||
|
"~:font-variant-id": "regular",
|
||||||
|
"~:text-decoration": "none",
|
||||||
|
"~:letter-spacing": "0",
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#000000",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:font-family": "sourcesanspro",
|
||||||
|
"~:text": "Uno dos tres cuatro"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:typography-ref-id": null,
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:text-align": "left",
|
||||||
|
"~:font-id": "sourcesanspro",
|
||||||
|
"~:key": "dqq7j",
|
||||||
|
"~:font-size": "44",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:typography-ref-file": null,
|
||||||
|
"~:text-direction": "ltr",
|
||||||
|
"~:type": "paragraph",
|
||||||
|
"~:font-variant-id": "regular",
|
||||||
|
"~:text-decoration": "none",
|
||||||
|
"~:letter-spacing": "0",
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#000000",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:font-family": "sourcesanspro"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"~:hide-in-viewer": false,
|
||||||
|
"~:name": "Uno dos tres cuatro",
|
||||||
|
"~:width": 360,
|
||||||
|
"~:type": "~:text",
|
||||||
|
"~:points": [
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1094,
|
||||||
|
"~:y": 856
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1454,
|
||||||
|
"~:y": 856
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1454,
|
||||||
|
"~:y": 909
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"~#point": {
|
||||||
|
"~:x": 1094,
|
||||||
|
"~:y": 909
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:transform-inverse": {
|
||||||
|
"~#matrix": {
|
||||||
|
"~:a": 1.0,
|
||||||
|
"~:b": 0.0,
|
||||||
|
"~:c": 0.0,
|
||||||
|
"~:d": 1.0,
|
||||||
|
"~:e": 0.0,
|
||||||
|
"~:f": 0.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:id": "~ud4467b11-7129-80c3-8005-934871e790b5",
|
||||||
|
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:position-data": [
|
||||||
|
{
|
||||||
|
"~#rect": {
|
||||||
|
"~:y": 910,
|
||||||
|
"~:font-style": "normal",
|
||||||
|
"~:text-transform": "none",
|
||||||
|
"~:font-size": "44px",
|
||||||
|
"~:font-weight": "400",
|
||||||
|
"~:y1": -3,
|
||||||
|
"~:width": 359.546875,
|
||||||
|
"~:text-decoration": "none solid rgb(0, 0, 0)",
|
||||||
|
"~:letter-spacing": "normal",
|
||||||
|
"~:x": 1094,
|
||||||
|
"~:x1": 0,
|
||||||
|
"~:y2": 54,
|
||||||
|
"~:fills": [
|
||||||
|
{
|
||||||
|
"~:fill-color": "#000000",
|
||||||
|
"~:fill-opacity": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:x2": 359.546875,
|
||||||
|
"~:direction": "ltr",
|
||||||
|
"~:font-family": "sourcesanspro",
|
||||||
|
"~:height": 57,
|
||||||
|
"~:text": "Uno dos tres cuatro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||||
|
"~:x": 1094,
|
||||||
|
"~:selrect": {
|
||||||
|
"~#rect": {
|
||||||
|
"~:x": 1094,
|
||||||
|
"~:y": 856,
|
||||||
|
"~:width": 360,
|
||||||
|
"~:height": 53,
|
||||||
|
"~:x1": 1094,
|
||||||
|
"~:y1": 856,
|
||||||
|
"~:x2": 1454,
|
||||||
|
"~:y2": 909
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:flip-x": null,
|
||||||
|
"~:height": 53,
|
||||||
|
"~:flip-y": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:id": "~u870f9f10-87b5-8137-8005-934804124661",
|
||||||
|
"~:name": "Page 1"
|
||||||
|
}
|
||||||
|
}
|
49
frontend/playwright/data/workspace/get-file-copy-paste.json
Normal file
49
frontend/playwright/data/workspace/get-file-copy-paste.json
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"~:features": {
|
||||||
|
"~#set": [
|
||||||
|
"layout/grid",
|
||||||
|
"fdata/pointer-map",
|
||||||
|
"fdata/objects-map",
|
||||||
|
"components/v2",
|
||||||
|
"fdata/shape-data-type"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"~:permissions": {
|
||||||
|
"~:type": "~:membership",
|
||||||
|
"~:is-owner": true,
|
||||||
|
"~:is-admin": true,
|
||||||
|
"~:can-edit": true,
|
||||||
|
"~:can-read": true,
|
||||||
|
"~:is-logged": true
|
||||||
|
},
|
||||||
|
"~:has-media-trimmed": false,
|
||||||
|
"~:comment-thread-seqn": 0,
|
||||||
|
"~:name": "New File 6",
|
||||||
|
"~:revn": 13,
|
||||||
|
"~:modified-at": "~m1736778551377",
|
||||||
|
"~:vern": 0,
|
||||||
|
"~:id": "~u870f9f10-87b5-8137-8005-934804124660",
|
||||||
|
"~:is-shared": false,
|
||||||
|
"~:version": 60,
|
||||||
|
"~:project-id": "~u3ffbd505-2f26-800f-8004-f34da98bdad8",
|
||||||
|
"~:created-at": "~m1736778427466",
|
||||||
|
"~:data": {
|
||||||
|
"~:pages": [
|
||||||
|
"~u870f9f10-87b5-8137-8005-934804124661"
|
||||||
|
],
|
||||||
|
"~:pages-index": {
|
||||||
|
"~u870f9f10-87b5-8137-8005-934804124661": {
|
||||||
|
"~#penpot/pointer": [
|
||||||
|
"~u870f9f10-87b5-8137-8005-93487d148645",
|
||||||
|
{
|
||||||
|
"~:created-at": "~m1736778551378"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"~:id": "~u870f9f10-87b5-8137-8005-934804124660",
|
||||||
|
"~:options": {
|
||||||
|
"~:components-v2": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -250,3 +250,60 @@ test("User have edition menu entries", async ({ page }) => {
|
||||||
await expect(page.getByText("Undo")).toBeVisible();
|
await expect(page.getByText("Undo")).toBeVisible();
|
||||||
await expect(page.getByText("Redo")).toBeVisible();
|
await expect(page.getByText("Redo")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Copy/paste properties", async ({ page, context }) => {
|
||||||
|
const workspacePage = new WorkspacePage(page);
|
||||||
|
await workspacePage.setupEmptyFile(page);
|
||||||
|
await workspacePage.mockRPC(
|
||||||
|
/get\-file\?/,
|
||||||
|
"workspace/get-file-copy-paste.json",
|
||||||
|
);
|
||||||
|
await workspacePage.mockRPC(
|
||||||
|
"get-file-fragment?file-id=*&fragment-id=*",
|
||||||
|
"workspace/get-file-copy-paste-fragment.json",
|
||||||
|
);
|
||||||
|
|
||||||
|
await workspacePage.goToWorkspace({
|
||||||
|
fileId: "870f9f10-87b5-8137-8005-934804124660",
|
||||||
|
pageId: "870f9f10-87b5-8137-8005-934804124661",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Access to the read/write clipboard necesary for this functionality
|
||||||
|
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||||
|
|
||||||
|
await page.getByTestId("layer-item").getByRole("button").first().click();
|
||||||
|
await page
|
||||||
|
.getByTestId("children-0eef4dd0-b39b-807a-8005-934805578f93")
|
||||||
|
.getByText("Rectangle")
|
||||||
|
.click({ button: "right" });
|
||||||
|
await page.getByText("Copy/Paste as").hover();
|
||||||
|
await page.getByText("Copy properties").click();
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByTestId("layer-item")
|
||||||
|
.getByText("Uno dos tres cuatro")
|
||||||
|
.click({ button: "right" });
|
||||||
|
await page.getByText("Copy/Paste as").hover();
|
||||||
|
await page.getByText("Paste properties").click();
|
||||||
|
|
||||||
|
await page.getByText("Rectangle").first().click({ button: "right" });
|
||||||
|
await page.getByText("Copy/Paste as").hover();
|
||||||
|
await page.getByText("Paste properties").click();
|
||||||
|
|
||||||
|
await page.getByText("Board").nth(2).click({ button: "right" });
|
||||||
|
await page.getByText("Copy/Paste as").hover();
|
||||||
|
await page.getByText("Paste properties").click();
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByTestId("layer-item")
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: "Path" })
|
||||||
|
.nth(1)
|
||||||
|
.click({ button: "right" });
|
||||||
|
await page.getByText("Copy/Paste as").hover();
|
||||||
|
await page.getByText("Paste properties").click();
|
||||||
|
|
||||||
|
await page.getByText("Ellipse").click({ button: "right" });
|
||||||
|
await page.getByText("Copy/Paste as").hover();
|
||||||
|
await page.getByText("Paste properties").click();
|
||||||
|
});
|
||||||
|
|
|
@ -81,6 +81,7 @@
|
||||||
[app.main.streams :as ms]
|
[app.main.streams :as ms]
|
||||||
[app.main.worker :as uw]
|
[app.main.worker :as uw]
|
||||||
[app.render-wasm :as wasm]
|
[app.render-wasm :as wasm]
|
||||||
|
[app.util.code-gen.style-css :as css]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.globals :as ug]
|
[app.util.globals :as ug]
|
||||||
[app.util.http :as http]
|
[app.util.http :as http]
|
||||||
|
@ -1411,7 +1412,8 @@
|
||||||
(rx/catch on-copy-error)
|
(rx/catch on-copy-error)
|
||||||
(rx/ignore))))))))))
|
(rx/ignore))))))))))
|
||||||
|
|
||||||
(declare ^:private paste-transit)
|
(declare ^:private paste-transit-shapes)
|
||||||
|
(declare ^:private paste-transit-props)
|
||||||
(declare ^:private paste-html-text)
|
(declare ^:private paste-html-text)
|
||||||
(declare ^:private paste-text)
|
(declare ^:private paste-text)
|
||||||
(declare ^:private paste-image)
|
(declare ^:private paste-image)
|
||||||
|
@ -1441,7 +1443,7 @@
|
||||||
(rx/of (paste-text data)))
|
(rx/of (paste-text data)))
|
||||||
|
|
||||||
:transit
|
:transit
|
||||||
(rx/of (paste-transit data))))
|
(rx/of (paste-transit-shapes data))))
|
||||||
|
|
||||||
(on-error [cause]
|
(on-error [cause]
|
||||||
(let [data (ex-data cause)]
|
(let [data (ex-data cause)]
|
||||||
|
@ -1462,7 +1464,6 @@
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
|
|
||||||
(defn paste-from-event
|
(defn paste-from-event
|
||||||
"Perform a `paste` operation from user emmited event."
|
"Perform a `paste` operation from user emmited event."
|
||||||
[event in-viewport?]
|
[event in-viewport?]
|
||||||
|
@ -1491,7 +1492,7 @@
|
||||||
(rx/map paste-image))
|
(rx/map paste-image))
|
||||||
|
|
||||||
(coll? transit-data)
|
(coll? transit-data)
|
||||||
(rx/of (paste-transit (assoc transit-data :in-viewport in-viewport?)))
|
(rx/of (paste-transit-shapes (assoc transit-data :in-viewport in-viewport?)))
|
||||||
|
|
||||||
(string? html-data)
|
(string? html-data)
|
||||||
(rx/of (paste-html-text html-data text-data))
|
(rx/of (paste-html-text html-data text-data))
|
||||||
|
@ -1502,6 +1503,122 @@
|
||||||
:else
|
:else
|
||||||
(rx/empty))))))))
|
(rx/empty))))))))
|
||||||
|
|
||||||
|
(defn copy-selected-css
|
||||||
|
[]
|
||||||
|
(ptk/reify ::copy-selected-css
|
||||||
|
ptk/EffectEvent
|
||||||
|
(effect [_ state _]
|
||||||
|
(let [objects (wsh/lookup-page-objects state)
|
||||||
|
selected (->> (wsh/lookup-selected state) (mapv (d/getf objects)))
|
||||||
|
css (css/generate-style objects selected selected {:with-prelude? false})]
|
||||||
|
(wapi/write-to-clipboard css)))))
|
||||||
|
|
||||||
|
(defn copy-selected-css-nested
|
||||||
|
[]
|
||||||
|
(ptk/reify ::copy-selected-css-nested
|
||||||
|
ptk/EffectEvent
|
||||||
|
(effect [_ state _]
|
||||||
|
(let [objects (wsh/lookup-page-objects state)
|
||||||
|
selected (->> (wsh/lookup-selected state)
|
||||||
|
(cfh/selected-with-children objects)
|
||||||
|
(mapv (d/getf objects)))
|
||||||
|
css (css/generate-style objects selected selected {:with-prelude? false})]
|
||||||
|
(wapi/write-to-clipboard css)))))
|
||||||
|
|
||||||
|
(defn copy-selected-props
|
||||||
|
[]
|
||||||
|
(ptk/reify ::copy-selected-props
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(letfn [(fetch-image [entry]
|
||||||
|
(let [url (cf/resolve-file-media entry)]
|
||||||
|
(->> (http/send! {:method :get
|
||||||
|
:uri url
|
||||||
|
:response-type :blob})
|
||||||
|
(rx/map :body)
|
||||||
|
(rx/mapcat wapi/read-file-as-data-url)
|
||||||
|
(rx/map #(assoc entry :data %)))))
|
||||||
|
|
||||||
|
(resolve-images [data]
|
||||||
|
(let [images
|
||||||
|
(concat
|
||||||
|
(->> data :props :fills (keep :fill-image))
|
||||||
|
(->> data :props :strokes (keep :stroke-image)))]
|
||||||
|
|
||||||
|
(if (seq images)
|
||||||
|
(->> (rx/from images)
|
||||||
|
(rx/mapcat fetch-image)
|
||||||
|
(rx/reduce conj #{})
|
||||||
|
(rx/map #(assoc data :images %)))
|
||||||
|
(rx/of data))))
|
||||||
|
|
||||||
|
(on-copy-error [error]
|
||||||
|
(js/console.error "clipboard blocked:" error)
|
||||||
|
(rx/empty))]
|
||||||
|
|
||||||
|
(let [selected (->> (wsh/lookup-selected state) first)
|
||||||
|
objects (wsh/lookup-page-objects state)]
|
||||||
|
|
||||||
|
(when-let [shape (get objects selected)]
|
||||||
|
(let [props (cts/extract-props shape)
|
||||||
|
features (-> (features/get-team-enabled-features state)
|
||||||
|
(set/difference cfeat/frontend-only-features))
|
||||||
|
version (dm/get-in state [:workspace-file :version])
|
||||||
|
|
||||||
|
copy-data {:type :copied-props
|
||||||
|
:features features
|
||||||
|
:version version
|
||||||
|
:props props
|
||||||
|
:images #{}}]
|
||||||
|
|
||||||
|
;; The clipboard API doesn't handle well asynchronous calls because it expects to use
|
||||||
|
;; the clipboard in an user interaction. If you do an async call the callback is outside
|
||||||
|
;; the thread of the UI and so Safari blocks the copying event.
|
||||||
|
;; We use the API `ClipboardItem` that allows promises to be passed and so the event
|
||||||
|
;; will wait for the promise to resolve and everything should work as expected.
|
||||||
|
;; This only works in the current versions of the browsers.
|
||||||
|
(if (some? (unchecked-get ug/global "ClipboardItem"))
|
||||||
|
(let [resolve-data-promise
|
||||||
|
(p/create
|
||||||
|
(fn [resolve reject]
|
||||||
|
(->> (rx/of copy-data)
|
||||||
|
(rx/mapcat resolve-images)
|
||||||
|
(rx/map #(t/encode-str % {:type :json-verbose}))
|
||||||
|
(rx/map #(wapi/create-blob % "text/plain"))
|
||||||
|
(rx/subs! resolve reject))))]
|
||||||
|
|
||||||
|
(->> (rx/from (wapi/write-to-clipboard-promise "text/plain" resolve-data-promise))
|
||||||
|
(rx/catch on-copy-error)
|
||||||
|
(rx/ignore)))
|
||||||
|
;; FIXME: this is to support Firefox versions below 116 that don't support
|
||||||
|
;; `ClipboardItem` after the version 116 is less common we could remove this.
|
||||||
|
;; https://caniuse.com/?search=ClipboardItem
|
||||||
|
(->> (rx/of copy-data)
|
||||||
|
(rx/mapcat resolve-images)
|
||||||
|
(rx/map #(wapi/write-to-clipboard (t/encode-str % {:type :json-verbose})))
|
||||||
|
(rx/catch on-copy-error)
|
||||||
|
(rx/ignore))))))))))
|
||||||
|
|
||||||
|
(defn paste-selected-props
|
||||||
|
[]
|
||||||
|
(ptk/reify ::paste-selected-props
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ _ _]
|
||||||
|
(letfn [(decode-entry [entry]
|
||||||
|
(-> entry t/decode-str paste-transit-props))
|
||||||
|
|
||||||
|
(on-error [cause]
|
||||||
|
(let [data (ex-data cause)]
|
||||||
|
(if (:not-implemented data)
|
||||||
|
(rx/of (ntf/warn (tr "errors.clipboard-not-implemented")))
|
||||||
|
(js/console.error "Clipboard error:" cause))
|
||||||
|
(rx/empty)))]
|
||||||
|
|
||||||
|
(->> (wapi/read-from-clipboard)
|
||||||
|
(rx/map decode-entry)
|
||||||
|
(rx/take 1)
|
||||||
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
(defn selected-frame? [state]
|
(defn selected-frame? [state]
|
||||||
(let [selected (wsh/lookup-selected state)
|
(let [selected (wsh/lookup-selected state)
|
||||||
objects (wsh/lookup-page-objects state)]
|
objects (wsh/lookup-page-objects state)]
|
||||||
|
@ -1530,8 +1647,8 @@
|
||||||
(:width (:selrect frame-obj)))))
|
(:width (:selrect frame-obj)))))
|
||||||
|
|
||||||
(def ^:private
|
(def ^:private
|
||||||
schema:paste-data
|
schema:paste-data-shapes
|
||||||
[:map {:title "paste-data"}
|
[:map {:title "paste-data-shapes"}
|
||||||
[:type [:= :copied-shapes]]
|
[:type [:= :copied-shapes]]
|
||||||
[:features ::sm/set-of-strings]
|
[:features ::sm/set-of-strings]
|
||||||
[:version :int]
|
[:version :int]
|
||||||
|
@ -1542,12 +1659,26 @@
|
||||||
[:images [:set :map]]
|
[:images [:set :map]]
|
||||||
[:position {:optional true} ::gpt/point]])
|
[:position {:optional true} ::gpt/point]])
|
||||||
|
|
||||||
|
(def ^:private
|
||||||
|
schema:paste-data-props
|
||||||
|
[:map {:title "paste-data-props"}
|
||||||
|
[:type [:= :copied-props]]
|
||||||
|
[:features ::sm/set-of-strings]
|
||||||
|
[:version :int]
|
||||||
|
[:props
|
||||||
|
;; todo type the properties
|
||||||
|
[:map-of :keyword :any]]])
|
||||||
|
|
||||||
|
(def schema:paste-data
|
||||||
|
[:multi {:title "paste-data" :dispatch :type}
|
||||||
|
[:copied-shapes schema:paste-data-shapes]
|
||||||
|
[:copied-props schema:paste-data-props]])
|
||||||
|
|
||||||
(def paste-data-valid?
|
(def paste-data-valid?
|
||||||
(sm/lazy-validator schema:paste-data))
|
(sm/lazy-validator schema:paste-data))
|
||||||
|
|
||||||
(defn- paste-transit
|
(defn- paste-transit-shapes
|
||||||
[{:keys [images] :as pdata}]
|
[{:keys [images] :as pdata}]
|
||||||
|
|
||||||
(letfn [(upload-media [file-id imgpart]
|
(letfn [(upload-media [file-id imgpart]
|
||||||
(->> (http/send! {:uri (:data imgpart)
|
(->> (http/send! {:uri (:data imgpart)
|
||||||
:response-type :blob
|
:response-type :blob
|
||||||
|
@ -1562,7 +1693,7 @@
|
||||||
(rx/mapcat (partial rp/cmd! :upload-file-media-object))
|
(rx/mapcat (partial rp/cmd! :upload-file-media-object))
|
||||||
(rx/map #(assoc % :prev-id (:id imgpart)))))]
|
(rx/map #(assoc % :prev-id (:id imgpart)))))]
|
||||||
|
|
||||||
(ptk/reify ::paste-transit
|
(ptk/reify ::paste-transit-shapes
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [file-id (:current-file-id state)
|
(let [file-id (:current-file-id state)
|
||||||
|
@ -1574,14 +1705,88 @@
|
||||||
:hibt "invalid paste data found"))
|
:hibt "invalid paste data found"))
|
||||||
|
|
||||||
(cfeat/check-paste-features! features (:features pdata))
|
(cfeat/check-paste-features! features (:features pdata))
|
||||||
(if (= file-id (:file-id pdata))
|
|
||||||
(let [pdata (assoc pdata :images [])]
|
(case (:type pdata)
|
||||||
(rx/of (paste-shapes pdata)))
|
:copied-shapes
|
||||||
(->> (rx/from images)
|
(if (= file-id (:file-id pdata))
|
||||||
|
(let [pdata (assoc pdata :images [])]
|
||||||
|
(rx/of (paste-shapes pdata)))
|
||||||
|
(->> (rx/from images)
|
||||||
|
(rx/merge-map (partial upload-media file-id))
|
||||||
|
(rx/reduce conj [])
|
||||||
|
(rx/map #(assoc pdata :images %))
|
||||||
|
(rx/map paste-shapes)))
|
||||||
|
nil))))))
|
||||||
|
|
||||||
|
(defn- paste-transit-props
|
||||||
|
[pdata]
|
||||||
|
|
||||||
|
(letfn [(upload-media [file-id imgpart]
|
||||||
|
(->> (http/send! {:uri (:data imgpart)
|
||||||
|
:response-type :blob
|
||||||
|
:method :get})
|
||||||
|
(rx/map :body)
|
||||||
|
(rx/map
|
||||||
|
(fn [blob]
|
||||||
|
{:name (:name imgpart)
|
||||||
|
:file-id file-id
|
||||||
|
:content blob
|
||||||
|
:is-local true}))
|
||||||
|
(rx/mapcat (partial rp/cmd! :upload-file-media-object))
|
||||||
|
(rx/map #(vector (:id imgpart) %))))
|
||||||
|
|
||||||
|
(update-image-data
|
||||||
|
[pdata media-map]
|
||||||
|
(update
|
||||||
|
pdata :props
|
||||||
|
(fn [props]
|
||||||
|
(-> props
|
||||||
|
(d/update-when
|
||||||
|
:fills
|
||||||
|
(fn [fills]
|
||||||
|
(mapv (fn [fill]
|
||||||
|
(cond-> fill
|
||||||
|
(some? (:fill-image fill))
|
||||||
|
(update-in [:fill-image :id] #(get media-map % %))))
|
||||||
|
fills)))
|
||||||
|
(d/update-when
|
||||||
|
:strokes
|
||||||
|
(fn [strokes]
|
||||||
|
(mapv (fn [stroke]
|
||||||
|
(cond-> stroke
|
||||||
|
(some? (:stroke-image stroke))
|
||||||
|
(update-in [:stroke-image :id] #(get media-map % %))))
|
||||||
|
strokes)))))))
|
||||||
|
|
||||||
|
(upload-images
|
||||||
|
[file-id pdata]
|
||||||
|
(->> (rx/from (:images pdata))
|
||||||
(rx/merge-map (partial upload-media file-id))
|
(rx/merge-map (partial upload-media file-id))
|
||||||
(rx/reduce conj [])
|
(rx/reduce conj {})
|
||||||
(rx/map #(assoc pdata :images %))
|
(rx/map (partial update-image-data pdata))))]
|
||||||
(rx/map paste-shapes))))))))
|
|
||||||
|
(ptk/reify ::paste-transit-props
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [features (features/get-team-enabled-features state)
|
||||||
|
selected (wsh/lookup-selected state)]
|
||||||
|
|
||||||
|
(when (paste-data-valid? pdata)
|
||||||
|
(cfeat/check-paste-features! features (:features pdata))
|
||||||
|
(case (:type pdata)
|
||||||
|
:copied-props
|
||||||
|
|
||||||
|
(rx/concat
|
||||||
|
(->> (rx/of pdata)
|
||||||
|
(rx/mapcat (partial upload-images (:current-file-id state)))
|
||||||
|
(rx/map
|
||||||
|
#(dwsh/update-shapes
|
||||||
|
selected
|
||||||
|
(fn [shape objects] (cts/patch-props shape (:props pdata) objects))
|
||||||
|
{:with-objects? true})))
|
||||||
|
(rx/of (ptk/data-event :layout/update {:ids selected})))
|
||||||
|
;;
|
||||||
|
(rx/empty))))))))
|
||||||
|
|
||||||
(defn paste-shapes
|
(defn paste-shapes
|
||||||
[{in-viewport? :in-viewport :as pdata}]
|
[{in-viewport? :in-viewport :as pdata}]
|
||||||
|
|
|
@ -85,8 +85,8 @@
|
||||||
:subsections [:edit]
|
:subsections [:edit]
|
||||||
:fn #(st/emit! (dw/copy-selected))}
|
:fn #(st/emit! (dw/copy-selected))}
|
||||||
|
|
||||||
:copy-link {:tooltip (ds/meta (ds/alt "C"))
|
:copy-link {:tooltip (ds/shift (ds/alt "C"))
|
||||||
:command (ds/c-mod "alt+c")
|
:command "shift+alt+c"
|
||||||
:subsections [:edit]
|
:subsections [:edit]
|
||||||
:fn #(st/emit! (dw/copy-link-to-clipboard))}
|
:fn #(st/emit! (dw/copy-link-to-clipboard))}
|
||||||
|
|
||||||
|
@ -103,6 +103,16 @@
|
||||||
:subsections [:edit]
|
:subsections [:edit]
|
||||||
:fn (constantly nil)}
|
:fn (constantly nil)}
|
||||||
|
|
||||||
|
:copy-props {:tooltip (ds/meta (ds/alt "c"))
|
||||||
|
:command (ds/c-mod "alt+c")
|
||||||
|
:subsections [:edit]
|
||||||
|
:fn #(st/emit! (dw/copy-selected-props))}
|
||||||
|
|
||||||
|
:paste-props {:tooltip (ds/meta (ds/alt "v"))
|
||||||
|
:command (ds/c-mod "alt+v")
|
||||||
|
:subsections [:edit]
|
||||||
|
:fn #(st/emit! (dw/paste-selected-props))}
|
||||||
|
|
||||||
:delete {:tooltip (ds/supr)
|
:delete {:tooltip (ds/supr)
|
||||||
:command ["del" "backspace"]
|
:command ["del" "backspace"]
|
||||||
:subsections [:edit]
|
:subsections [:edit]
|
||||||
|
|
|
@ -321,7 +321,7 @@
|
||||||
|
|
||||||
.comment-input {
|
.comment-input {
|
||||||
@include bodySmallTypography;
|
@include bodySmallTypography;
|
||||||
white-space: pre;
|
white-space: pre-line;
|
||||||
background: var(--input-background-color);
|
background: var(--input-background-color);
|
||||||
border-radius: $br-8;
|
border-radius: $br-8;
|
||||||
border: $s-1 solid var(--input-border-color);
|
border: $s-1 solid var(--input-border-color);
|
||||||
|
|
|
@ -11,10 +11,12 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.files.helpers :as cfh]
|
[app.common.files.helpers :as cfh]
|
||||||
|
[app.common.transit :as t]
|
||||||
[app.common.types.component :as ctk]
|
[app.common.types.component :as ctk]
|
||||||
[app.common.types.container :as ctn]
|
[app.common.types.container :as ctn]
|
||||||
[app.common.types.page :as ctp]
|
[app.common.types.page :as ctp]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
|
[app.config :as cf]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.shortcuts :as scd]
|
[app.main.data.shortcuts :as scd]
|
||||||
|
@ -35,6 +37,8 @@
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :refer [tr] :as i18n]
|
[app.util.i18n :refer [tr] :as i18n]
|
||||||
[app.util.timers :as timers]
|
[app.util.timers :as timers]
|
||||||
|
[app.util.webapi :as wapi]
|
||||||
|
[beicon.v2.core :as rx]
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
[potok.v2.core :as ptk]
|
[potok.v2.core :as ptk]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
@ -141,12 +145,44 @@
|
||||||
do-cut #(st/emit! (dw/copy-selected)
|
do-cut #(st/emit! (dw/copy-selected)
|
||||||
(dw/delete-selected))
|
(dw/delete-selected))
|
||||||
do-paste #(st/emit! (dw/paste-from-clipboard))
|
do-paste #(st/emit! (dw/paste-from-clipboard))
|
||||||
do-duplicate #(st/emit! (dw/duplicate-selected true))]
|
do-duplicate #(st/emit! (dw/duplicate-selected true))
|
||||||
|
|
||||||
|
enabled-paste-props* (mf/use-state false)
|
||||||
|
|
||||||
|
handle-copy-css
|
||||||
|
(mf/use-callback #(st/emit! (dw/copy-selected-css)))
|
||||||
|
|
||||||
|
handle-copy-css-nested
|
||||||
|
(mf/use-callback #(st/emit! (dw/copy-selected-css-nested)))
|
||||||
|
|
||||||
|
handle-copy-props
|
||||||
|
(mf/use-callback #(st/emit! (dw/copy-selected-props)))
|
||||||
|
|
||||||
|
handle-paste-props
|
||||||
|
(mf/use-callback #(st/emit! (dw/paste-selected-props)))
|
||||||
|
|
||||||
|
handle-hover-copy-paste
|
||||||
|
(mf/use-callback
|
||||||
|
(fn []
|
||||||
|
(->> (wapi/read-from-clipboard)
|
||||||
|
(rx/take 1)
|
||||||
|
(rx/subs!
|
||||||
|
(fn [data]
|
||||||
|
(try
|
||||||
|
(let [pdata (t/decode-str data)]
|
||||||
|
(reset! enabled-paste-props*
|
||||||
|
(and (dw/paste-data-valid? pdata)
|
||||||
|
(= :copied-props (:type pdata)))))
|
||||||
|
(catch :default _
|
||||||
|
(reset! enabled-paste-props* false))))
|
||||||
|
(fn []
|
||||||
|
(reset! enabled-paste-props* false))))))]
|
||||||
|
|
||||||
[:*
|
[:*
|
||||||
[:> menu-entry* {:title (tr "workspace.shape.menu.copy")
|
[:> menu-entry* {:title (tr "workspace.shape.menu.copy")
|
||||||
:shortcut (sc/get-tooltip :copy)
|
:shortcut (sc/get-tooltip :copy)
|
||||||
:on-click do-copy}]
|
:on-click do-copy}]
|
||||||
[:> menu-entry* {:title (tr "workspace.shape.menu.copy_link")
|
[:> menu-entry* {:title (tr "workspace.shape.menu.copy-link")
|
||||||
:shortcut (sc/get-tooltip :copy-link)
|
:shortcut (sc/get-tooltip :copy-link)
|
||||||
:on-click do-copy-link}]
|
:on-click do-copy-link}]
|
||||||
[:> menu-entry* {:title (tr "workspace.shape.menu.cut")
|
[:> menu-entry* {:title (tr "workspace.shape.menu.cut")
|
||||||
|
@ -159,6 +195,23 @@
|
||||||
:shortcut (sc/get-tooltip :duplicate)
|
:shortcut (sc/get-tooltip :duplicate)
|
||||||
:on-click do-duplicate}]
|
:on-click do-duplicate}]
|
||||||
|
|
||||||
|
[:> menu-entry* {:title (tr "workspace.shape.menu.copy-paste-as")
|
||||||
|
:on-pointer-enter (when (cf/check-browser? :chrome) handle-hover-copy-paste)}
|
||||||
|
[:> menu-entry* {:title (tr "workspace.shape.menu.copy-css")
|
||||||
|
:on-click handle-copy-css}]
|
||||||
|
[:> menu-entry* {:title (tr "workspace.shape.menu.copy-css-nested")
|
||||||
|
:on-click handle-copy-css-nested}]
|
||||||
|
|
||||||
|
[:> menu-separator* {}]
|
||||||
|
|
||||||
|
[:> menu-entry* {:title (tr "workspace.shape.menu.copy-props")
|
||||||
|
:shortcut (sc/get-tooltip :copy-props)
|
||||||
|
:on-click handle-copy-props}]
|
||||||
|
[:> menu-entry* {:title (tr "workspace.shape.menu.paste-props")
|
||||||
|
:shortcut (sc/get-tooltip :paste-props)
|
||||||
|
:disabled (and (cf/check-browser? :chrome) (not @enabled-paste-props*))
|
||||||
|
:on-click handle-paste-props}]]
|
||||||
|
|
||||||
[:> menu-separator* {}]]))
|
[:> menu-separator* {}]]))
|
||||||
|
|
||||||
(mf/defc context-menu-layer-position*
|
(mf/defc context-menu-layer-position*
|
||||||
|
|
|
@ -5991,9 +5991,24 @@ msgid "workspace.shape.menu.copy"
|
||||||
msgstr "Copy"
|
msgstr "Copy"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/context_menu.cljs:146
|
#: src/app/main/ui/workspace/context_menu.cljs:146
|
||||||
msgid "workspace.shape.menu.copy_link"
|
msgid "workspace.shape.menu.copy-link"
|
||||||
msgstr "Copy link to clipboard"
|
msgstr "Copy link to clipboard"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.copy-paste-as"
|
||||||
|
msgstr "Copy/Paste as ..."
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.copy-css"
|
||||||
|
msgstr "Copy as CSS"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.copy-css-nested"
|
||||||
|
msgstr "Copy as CSS (nested layers)"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.copy-props"
|
||||||
|
msgstr "Copy properties"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.paste-props"
|
||||||
|
msgstr "Paste properties"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/assets/common.cljs:431
|
#: src/app/main/ui/workspace/sidebar/assets/common.cljs:431
|
||||||
msgid "workspace.shape.menu.create-annotation"
|
msgid "workspace.shape.menu.create-annotation"
|
||||||
msgstr "Create annotation"
|
msgstr "Create annotation"
|
||||||
|
@ -6956,3 +6971,4 @@ msgstr "Notifications"
|
||||||
|
|
||||||
msgid "comments.mentions.not-found"
|
msgid "comments.mentions.not-found"
|
||||||
msgstr "No people found for @%s"
|
msgstr "No people found for @%s"
|
||||||
|
|
||||||
|
|
|
@ -5990,9 +5990,24 @@ msgstr "Enviar atrás"
|
||||||
msgid "workspace.shape.menu.copy"
|
msgid "workspace.shape.menu.copy"
|
||||||
msgstr "Copiar"
|
msgstr "Copiar"
|
||||||
|
|
||||||
msgid "workspace.shape.menu.copy_link"
|
msgid "workspace.shape.menu.copy-link"
|
||||||
msgstr "Copiar enlace al portapapeles"
|
msgstr "Copiar enlace al portapapeles"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.copy-paste-as"
|
||||||
|
msgstr "Copiar/Pegar como ..."
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.copy-css"
|
||||||
|
msgstr "Copiar como CSS"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.copy-css-nested"
|
||||||
|
msgstr "Copiar como CSS (capas anidadas)"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.copy-props"
|
||||||
|
msgstr "Copiar propiedades"
|
||||||
|
|
||||||
|
msgid "workspace.shape.menu.paste-props"
|
||||||
|
msgstr "Pegar propiedades"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/assets/common.cljs:427
|
#: src/app/main/ui/workspace/sidebar/assets/common.cljs:427
|
||||||
msgid "workspace.shape.menu.create-annotation"
|
msgid "workspace.shape.menu.create-annotation"
|
||||||
msgstr "Crear una nota"
|
msgstr "Crear una nota"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue