mirror of
https://github.com/penpot/penpot.git
synced 2025-03-27 15:11:26 -05:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
7e87362a39
48 changed files with 995 additions and 145 deletions
|
@ -26,9 +26,13 @@
|
|||
- Allow library colors as recent colors [Taiga #7640](https://tree.taiga.io/project/penpot/issue/7640)
|
||||
- Missing scroll in viewmode comments [Taiga #7427](https://tree.taiga.io/project/penpot/issue/7427)
|
||||
- Comments in View mode should mimic the positioning behavior of the Workspace [Taiga #7346](https://tree.taiga.io/project/penpot/issue/7346)
|
||||
- Misaligned input on comments [Taiga #7461](https://tree.taiga.io/project/penpot/issue/7461)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix selection rectangle appears on scroll [Taiga #7525](https://tree.taiga.io/project/penpot/issue/7525)
|
||||
- Fix layer tree not expanding to the bottom edge [Taiga #7466](https://tree.taiga.io/project/penpot/issue/7466)
|
||||
- Fix guides move when board is moved by inputs [Taiga #8010](https://tree.taiga.io/project/penpot/issue/8010)
|
||||
- Fix clickable area of Penptot logo in the viewer [Taiga #7988](https://tree.taiga.io/project/penpot/issue/7988)
|
||||
- Fix constraints dropdown when selecting multiple shapes [Taiga #7686](https://tree.taiga.io/project/penpot/issue/7686)
|
||||
- Layout and scrollign fixes for the bottom palette [Taiga #7559](https://tree.taiga.io/project/penpot/issue/7559)
|
||||
|
@ -43,7 +47,12 @@
|
|||
- Fix "Attribute overrides in copies are not exported in zip file" [Taiga #8072](https://tree.taiga.io/project/penpot/issue/8072)
|
||||
- Fix group not automatically selected in the Layers panel after creation [Taiga #8078](https://tree.taiga.io/project/penpot/issue/8078)
|
||||
- Fix export boards loses opacity [Taiga #7592](https://tree.taiga.io/project/penpot/issue/7592)
|
||||
- Fix change color on imported svg also changes the stroke alignment[Taiga #7673](https://github.com/penpot/penpot/pull/7673)
|
||||
- Fix show in view mode and interactions workflow [Taiga #4711](https://github.com/penpot/penpot/pull/4711)
|
||||
- Fix internal error when I set up a stroke for some objects without and with stroke [Taiga #7558](https://tree.taiga.io/project/penpot/issue/7558)
|
||||
- Toolbar keeps toggling on and off on spacebar press [Taiga #7654](https://github.com/penpot/penpot/pull/7654)
|
||||
- Fix toolbar keeps hiding when click outside workspace [Taiga #7776](https://tree.taiga.io/project/penpot/issue/7776)
|
||||
- Fix open overlay relative to a frame [Taiga #7563](https://tree.taiga.io/project/penpot/issue/7563)
|
||||
|
||||
## 2.0.3
|
||||
|
||||
|
|
|
@ -2,13 +2,19 @@
|
|||
|
||||
We want to thank to the amazing people that help us! Thank you! You're the best!
|
||||
|
||||
Feel free you make a PR updating this file if you miss you in the
|
||||
list.
|
||||
|
||||
## Security
|
||||
|
||||
* Husnain Iqbal (CEO OF ALPHA INFERNO PVT LTD)
|
||||
* [Shiraz Ali Khan](https://www.linkedin.com/in/shiraz-ali-khan-1ba508180/)
|
||||
* Vaibhav Shukla
|
||||
* Hassan Ahmed (Alias Xen Lee)
|
||||
* Michal Biesiada (@mbiesiad)
|
||||
|
||||
## Internationalization
|
||||
|
||||
* [00ff88](https://hosted.weblate.org/user/00ff88)
|
||||
* [AhmadHB](https://hosted.weblate.org/user/AhmadHB)
|
||||
* [Aimee](https://hosted.weblate.org/user/Aimee)
|
||||
|
@ -90,6 +96,7 @@ We want to thank to the amazing people that help us! Thank you! You're the best!
|
|||
* [zcraber](https://hosted.weblate.org/user/zcraber)
|
||||
|
||||
## Libraries & templates
|
||||
|
||||
* systxema
|
||||
* plumilla
|
||||
* victor crespo
|
||||
|
|
|
@ -87,7 +87,10 @@
|
|||
:ldap-attrs-fullname "cn"
|
||||
|
||||
;; a server prop key where initial project is stored.
|
||||
:initial-project-skey "initial-project"})
|
||||
:initial-project-skey "initial-project"
|
||||
|
||||
;; time to avoid email sending after profile modification
|
||||
:email-verify-threshold "15m"})
|
||||
|
||||
(s/def ::default-rpc-rlimit ::us/vector-of-strings)
|
||||
(s/def ::rpc-rlimit-config ::fs/path)
|
||||
|
@ -213,6 +216,7 @@
|
|||
(s/def ::telemetry-uri ::us/string)
|
||||
(s/def ::telemetry-with-taiga ::us/boolean)
|
||||
(s/def ::tenant ::us/string)
|
||||
(s/def ::email-verify-threshold ::dt/duration)
|
||||
|
||||
(s/def ::config
|
||||
(s/keys :opt-un [::secret-key
|
||||
|
@ -334,7 +338,8 @@
|
|||
::telemetry-uri
|
||||
::telemetry-referer
|
||||
::telemetry-with-taiga
|
||||
::tenant]))
|
||||
::tenant
|
||||
::email-verify-threshold]))
|
||||
|
||||
(def default-flags
|
||||
[:enable-backend-api-doc
|
||||
|
|
|
@ -38,13 +38,11 @@
|
|||
(def schema:token
|
||||
[::sm/word-string {:max 6000}])
|
||||
|
||||
(def ^:private default-verify-threshold
|
||||
(dt/duration "15m"))
|
||||
|
||||
(defn- elapsed-verify-threshold?
|
||||
[profile]
|
||||
(let [elapsed (dt/diff (:modified-at profile) (dt/now))]
|
||||
(pos? (compare elapsed default-verify-threshold))))
|
||||
(let [elapsed (dt/diff (:modified-at profile) (dt/now))
|
||||
verify-threshold (cf/get :email-verify-threshold)]
|
||||
(pos? (compare elapsed verify-threshold))))
|
||||
|
||||
;; ---- COMMAND: login with password
|
||||
|
||||
|
@ -130,12 +128,21 @@
|
|||
|
||||
;; ---- COMMAND: Logout
|
||||
|
||||
(def ^:private schema:logout
|
||||
[:map {:title "logoug"}
|
||||
[:profile-id {:optional true} ::sm/uuid]])
|
||||
|
||||
(sv/defmethod ::logout
|
||||
"Clears the authentication cookie and logout the current session."
|
||||
{::rpc/auth false
|
||||
::doc/added "1.15"}
|
||||
[cfg _]
|
||||
(rph/with-transform {} (session/delete-fn cfg)))
|
||||
::doc/changes [["2.1" "Now requires profile-id passed in the body"]]
|
||||
::doc/added "1.0"
|
||||
::sm/params schema:logout}
|
||||
[cfg params]
|
||||
(if (= (:profile-id params)
|
||||
(::rpc/profile-id params))
|
||||
(rph/with-transform {} (session/delete-fn cfg))
|
||||
{}))
|
||||
|
||||
;; ---- COMMAND: Recover Profile
|
||||
|
||||
|
|
|
@ -184,10 +184,7 @@
|
|||
(ctk/instance-head? child))
|
||||
(let [slot (guess-swap-slot component-child component-container)]
|
||||
(l/dbg :hint "child" :id (:id child) :name (:name child) :slot slot)
|
||||
(ctn/update-shape container (:id child)
|
||||
#(update % :touched
|
||||
cfh/set-touched-group
|
||||
(ctk/build-swap-slot-group slot))))
|
||||
(ctn/update-shape container (:id child) #(ctk/set-swap-slot % slot)))
|
||||
container)]
|
||||
(recur (process-copy-head container child)
|
||||
(rest children)
|
||||
|
|
|
@ -481,7 +481,7 @@
|
|||
(let [slot (:swap-slot args)]
|
||||
(when (some? slot)
|
||||
(log/debug :hint (str " -> set swap-slot to " slot))
|
||||
(update shape :touched cfh/set-touched-group (ctk/build-swap-slot-group slot)))))]
|
||||
(ctk/set-swap-slot shape slot))))]
|
||||
|
||||
(log/dbg :hint "repairing shape :missing-slot" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
|
|
|
@ -284,9 +284,17 @@
|
|||
(let [children (cfh/get-children-with-self (:objects container) shape-id)
|
||||
skip-near (fn [changes shape]
|
||||
(let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})]
|
||||
(if (some? (:shape-ref ref-shape))
|
||||
(pcb/update-shapes changes [(:id shape)] #(assoc % :shape-ref (:shape-ref ref-shape)))
|
||||
changes)))]
|
||||
(cond-> changes
|
||||
(some? (:shape-ref ref-shape))
|
||||
(pcb/update-shapes [(:id shape)] #(assoc % :shape-ref (:shape-ref ref-shape)))
|
||||
|
||||
;; When advancing level, if the referenced shape has a swap slot, it must be
|
||||
;; copied to the current shape, because the shape-ref now will not be pointing
|
||||
;; to a near main (except for first level subcopies).
|
||||
(and (some? (ctk/get-swap-slot ref-shape))
|
||||
(nil? (ctk/get-swap-slot shape))
|
||||
(not= (:id shape) shape-id))
|
||||
(pcb/update-shapes [(:id shape)] #(ctk/set-swap-slot % (ctk/get-swap-slot ref-shape))))))]
|
||||
(reduce skip-near changes children)))
|
||||
|
||||
(defn prepare-restore-component
|
||||
|
@ -1194,7 +1202,7 @@
|
|||
:shapes all-parents}))
|
||||
changes' (reduce del-obj-change changes' new-shapes)]
|
||||
|
||||
(if (and (cfh/touched-group? parent-shape :shapes-group) omit-touched?)
|
||||
(if (and (ctk/touched-group? parent-shape :shapes-group) omit-touched?)
|
||||
changes
|
||||
changes')))
|
||||
|
||||
|
@ -1349,7 +1357,7 @@
|
|||
changes'
|
||||
ids)]
|
||||
|
||||
(if (and (cfh/touched-group? parent :shapes-group) omit-touched?)
|
||||
(if (and (ctk/touched-group? parent :shapes-group) omit-touched?)
|
||||
changes
|
||||
changes')))
|
||||
|
||||
|
@ -1385,7 +1393,7 @@
|
|||
:ignore-touched true
|
||||
:syncing true})))]
|
||||
|
||||
(if (and (cfh/touched-group? parent :shapes-group) omit-touched?)
|
||||
(if (and (ctk/touched-group? parent :shapes-group) omit-touched?)
|
||||
changes
|
||||
changes')))
|
||||
|
||||
|
@ -1846,12 +1854,11 @@
|
|||
;; if the shape isn't inside a main component, it shouldn't have a swap slot
|
||||
(and (nil? (ctk/get-swap-slot new-shape))
|
||||
inside-comp?)
|
||||
(update :touched cfh/set-touched-group (-> (ctf/find-swap-slot shape
|
||||
page
|
||||
{:id (:id file)
|
||||
:data file}
|
||||
libraries)
|
||||
(ctk/build-swap-slot-group))))]
|
||||
(ctk/set-swap-slot (ctf/find-swap-slot shape
|
||||
page
|
||||
{:id (:id file)
|
||||
:data file}
|
||||
libraries)))]
|
||||
|
||||
[new-shape (-> changes
|
||||
;; Restore the properties
|
||||
|
|
|
@ -183,6 +183,15 @@
|
|||
(and (= shape-id (:main-instance-id component))
|
||||
(= page-id (:main-instance-page component))))
|
||||
|
||||
(defn set-touched-group
|
||||
[touched group]
|
||||
(when group
|
||||
(conj (or touched #{}) group)))
|
||||
|
||||
(defn touched-group?
|
||||
[shape group]
|
||||
((or (:touched shape) #{}) group))
|
||||
|
||||
(defn build-swap-slot-group
|
||||
"Convert a swap-slot into a :touched group"
|
||||
[swap-slot]
|
||||
|
@ -204,6 +213,13 @@
|
|||
(when group
|
||||
(group->swap-slot group))))
|
||||
|
||||
(defn set-swap-slot
|
||||
"Add a touched group with a form :swap-slot-<uuid>."
|
||||
[shape swap-slot]
|
||||
(cond-> shape
|
||||
(some? swap-slot)
|
||||
(update :touched set-touched-group (build-swap-slot-group swap-slot))))
|
||||
|
||||
(defn match-swap-slot?
|
||||
[shape-main shape-inst]
|
||||
(let [slot-main (get-swap-slot shape-main)
|
||||
|
|
|
@ -0,0 +1,343 @@
|
|||
{
|
||||
"~:features":{
|
||||
"~#set":[
|
||||
"layout/grid",
|
||||
"styles/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 12",
|
||||
"~:revn":2,
|
||||
"~:modified-at":"~m1718012938567",
|
||||
"~:id":"~u1795a568-0df0-8095-8004-7ba741f56be2",
|
||||
"~:is-shared":false,
|
||||
"~:version":48,
|
||||
"~:project-id":"~u4dc640b0-5cbf-11ec-a7c5-91e9eb4f238d",
|
||||
"~:created-at":"~m1718012912598",
|
||||
"~:data":{
|
||||
"~:pages":[
|
||||
"~u1795a568-0df0-8095-8004-7ba741f56be3"
|
||||
],
|
||||
"~:pages-index":{
|
||||
"~u1795a568-0df0-8095-8004-7ba741f56be3":{
|
||||
"~: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,
|
||||
"~:y":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":0.01,
|
||||
"~:y":0
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":0.01,
|
||||
"~:y":0.01
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":0,
|
||||
"~:y":0.01
|
||||
}
|
||||
}
|
||||
],
|
||||
"~: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":"~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,
|
||||
"~: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":[
|
||||
"~u2ace9ce8-8e01-8086-8004-7ba745d4305a",
|
||||
"~u2ace9ce8-8e01-8086-8004-7ba748566e02"
|
||||
]
|
||||
}
|
||||
},
|
||||
"~u2ace9ce8-8e01-8086-8004-7ba745d4305a":{
|
||||
"~#shape":{
|
||||
"~:y":221,
|
||||
"~:rx":0,
|
||||
"~: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":105,
|
||||
"~:type":"~:rect",
|
||||
"~:points":[
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":165,
|
||||
"~:y":221
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":270,
|
||||
"~:y":221
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":270,
|
||||
"~:y":316
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":165,
|
||||
"~:y":316
|
||||
}
|
||||
}
|
||||
],
|
||||
"~: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":"~u2ace9ce8-8e01-8086-8004-7ba745d4305a",
|
||||
"~:parent-id":"~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id":"~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes":[
|
||||
|
||||
],
|
||||
"~:x":165,
|
||||
"~:proportion":1,
|
||||
"~:selrect":{
|
||||
"~#rect":{
|
||||
"~:x":165,
|
||||
"~:y":221,
|
||||
"~:width":105,
|
||||
"~:height":95,
|
||||
"~:x1":165,
|
||||
"~:y1":221,
|
||||
"~:x2":270,
|
||||
"~:y2":316
|
||||
}
|
||||
},
|
||||
"~:fills":[
|
||||
{
|
||||
"~:fill-color":"#B1B2B5",
|
||||
"~:fill-opacity":1
|
||||
}
|
||||
],
|
||||
"~:flip-x":null,
|
||||
"~:ry":0,
|
||||
"~:height":95,
|
||||
"~:flip-y":null
|
||||
}
|
||||
},
|
||||
"~u2ace9ce8-8e01-8086-8004-7ba748566e02":{
|
||||
"~#shape":{
|
||||
"~:y":228,
|
||||
"~: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":85,
|
||||
"~:type":"~:circle",
|
||||
"~:points":[
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":344,
|
||||
"~:y":228
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":429,
|
||||
"~:y":228
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":429,
|
||||
"~:y":308
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point":{
|
||||
"~:x":344,
|
||||
"~:y":308
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:proportion-lock":false,
|
||||
"~:transform-inverse":{
|
||||
"~#matrix":{
|
||||
"~:a":1.0,
|
||||
"~:b":0.0,
|
||||
"~:c":0.0,
|
||||
"~:d":1.0,
|
||||
"~:e":0.0,
|
||||
"~:f":0.0
|
||||
}
|
||||
},
|
||||
"~:blur":{
|
||||
"~:id":"~u2ace9ce8-8e01-8086-8004-7ba757cdd271",
|
||||
"~:type":"~:layer-blur",
|
||||
"~:value":4,
|
||||
"~:hidden":false
|
||||
},
|
||||
"~:id":"~u2ace9ce8-8e01-8086-8004-7ba748566e02",
|
||||
"~:parent-id":"~u00000000-0000-0000-0000-000000000000",
|
||||
"~:frame-id":"~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes":[
|
||||
{
|
||||
"~:stroke-alignment":"~:inner",
|
||||
"~:stroke-style":"~:solid",
|
||||
"~:stroke-color":"#000000",
|
||||
"~:stroke-opacity":1,
|
||||
"~:stroke-width":1
|
||||
}
|
||||
],
|
||||
"~:x":344,
|
||||
"~:proportion":1,
|
||||
"~:shadow":[
|
||||
{
|
||||
"~:color":{
|
||||
"~:color":"#000000",
|
||||
"~:opacity":0.2
|
||||
},
|
||||
"~:spread":0,
|
||||
"~:offset-y":4,
|
||||
"~:style":"~:drop-shadow",
|
||||
"~:blur":4,
|
||||
"~:hidden":false,
|
||||
"~:id":"~u2ace9ce8-8e01-8086-8004-7ba756ddebd5",
|
||||
"~:offset-x":4
|
||||
}
|
||||
],
|
||||
"~:selrect":{
|
||||
"~#rect":{
|
||||
"~:x":344,
|
||||
"~:y":228,
|
||||
"~:width":85,
|
||||
"~:height":80,
|
||||
"~:x1":344,
|
||||
"~:y1":228,
|
||||
"~:x2":429,
|
||||
"~:y2":308
|
||||
}
|
||||
},
|
||||
"~:fills":[
|
||||
{
|
||||
"~:fill-color":"#1247e7",
|
||||
"~:fill-opacity":1
|
||||
}
|
||||
],
|
||||
"~:flip-x":null,
|
||||
"~:height":80,
|
||||
"~:flip-y":null
|
||||
}
|
||||
}
|
||||
},
|
||||
"~:id":"~u1795a568-0df0-8095-8004-7ba741f56be3",
|
||||
"~:name":"Page 1"
|
||||
}
|
||||
},
|
||||
"~:id":"~u1795a568-0df0-8095-8004-7ba741f56be2",
|
||||
"~:recent-colors":[
|
||||
{
|
||||
"~:color":"#1247e7",
|
||||
"~:opacity":1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -4,11 +4,6 @@ export class DashboardPage extends BaseWebSocketPage {
|
|||
static async init(page) {
|
||||
await BaseWebSocketPage.initWebSockets(page);
|
||||
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
"get-profile",
|
||||
"logged-in-user/get-profile-logged-in-no-onboarding.json",
|
||||
);
|
||||
await BaseWebSocketPage.mockRPC(page, "get-teams", "logged-in-user/get-teams-default.json");
|
||||
await BaseWebSocketPage.mockRPC(
|
||||
page,
|
||||
|
|
45
frontend/playwright/ui/pages/OnboardingPage.js
Normal file
45
frontend/playwright/ui/pages/OnboardingPage.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { BaseWebSocketPage } from "./BaseWebSocketPage";
|
||||
|
||||
export class OnboardingPage extends BaseWebSocketPage {
|
||||
constructor(page) {
|
||||
super(page);
|
||||
this.submitButton = page.getByRole("Button",{ name: "Next" })
|
||||
}
|
||||
|
||||
async fillOnboardingInputsStep1() {
|
||||
await this.page.getByText('Personal').click();
|
||||
await this.page.getByText('Select option').click();
|
||||
await this.page.getByText('Testing before self-hosting').click();
|
||||
|
||||
await this.submitButton.click();
|
||||
}
|
||||
|
||||
async fillOnboardingInputsStep2() {
|
||||
await this.page.getByText('Figma').click();
|
||||
|
||||
await this.submitButton.click();
|
||||
}
|
||||
|
||||
async fillOnboardingInputsStep3() {
|
||||
await this.page.getByText('Select option').first().click();
|
||||
await this.page.getByText('Product Managment').click();
|
||||
await this.page.getByText('Select option').first().click();
|
||||
await this.page.getByText('Director').click();
|
||||
await this.page.getByText('Select option').click();
|
||||
await this.page.getByText('11-30').click();
|
||||
|
||||
await this.submitButton.click();
|
||||
}
|
||||
|
||||
async fillOnboardingInputsStep4() {
|
||||
await this.page.getByText('Other').click();
|
||||
await this.page.getByPlaceholder('Other (specify)').fill("Another");
|
||||
await this.submitButton.click();
|
||||
}
|
||||
|
||||
async fillOnboardingInputsStep5() {
|
||||
await this.page.getByText('Event').click();
|
||||
}
|
||||
}
|
||||
|
||||
export default OnboardingPage;
|
|
@ -43,13 +43,15 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||
this.presentUserListItems = page.getByTestId("active-users-list").getByAltText("Princesa Leia");
|
||||
this.viewport = page.getByTestId("viewport");
|
||||
this.rootShape = page.locator(`[id="shape-00000000-0000-0000-0000-000000000000"]`);
|
||||
this.toolbarOptions = page.getByTestId("toolbar-options");
|
||||
this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" });
|
||||
this.toggleToolbarButton = page.getByRole("button", { name: "Toggle toolbar" });
|
||||
this.colorpicker = page.getByTestId("colorpicker");
|
||||
this.layers = page.getByTestId("layers");
|
||||
this.layers = page.getByTestId("layer-tree");
|
||||
this.palette = page.getByTestId("palette");
|
||||
this.assets = page.getByTestId("assets");
|
||||
this.libraries = page.getByTestId("libraries");
|
||||
this.closeLibraries = page.getByTestId("close-libraries");
|
||||
this.sidebar = page.getByTestId("left-sidebar");
|
||||
this.selectionRect = page.getByTestId("workspace-selection-rect");
|
||||
this.horizontalScrollbar = page.getByTestId("horizontal-scrollbar");
|
||||
this.librariesModal = page.getByTestId("libraries-modal");
|
||||
}
|
||||
|
||||
|
@ -102,6 +104,19 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||
await this.page.mouse.up();
|
||||
}
|
||||
|
||||
async panOnViewportAt(x, y, width, height) {
|
||||
await this.page.waitForTimeout(100);
|
||||
await this.viewport.hover({ position: { x, y } });
|
||||
await this.page.mouse.down({ button: "middle" });
|
||||
await this.viewport.hover({ position: { x: x + width, y: y + height } });
|
||||
await this.page.mouse.up({ button: "middle" });
|
||||
}
|
||||
|
||||
async togglePages() {
|
||||
const pagesToggle = this.page.getByText("Pages");
|
||||
await pagesToggle.click();
|
||||
}
|
||||
|
||||
async moveSelectionToShape(name) {
|
||||
await this.page.locator('rect.viewport-selrect').hover();
|
||||
await this.page.mouse.down();
|
||||
|
@ -120,15 +135,21 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||
}
|
||||
|
||||
async expectSelectedLayer(name) {
|
||||
await expect(this.layers.getByTestId("layer-row").filter({ has: this.page.getByText(name) })).toHaveClass(/selected/);
|
||||
await expect(this.layers.getByTestId("layer-row").filter({ has: this.page.getByText(name) })).toHaveClass(
|
||||
/selected/,
|
||||
);
|
||||
}
|
||||
|
||||
async expectHiddenToolbarOptions() {
|
||||
await expect(this.toolbarOptions).toHaveCSS("opacity", "0");
|
||||
}
|
||||
|
||||
async clickAssets(clickOptions = {}) {
|
||||
await this.assets.click(clickOptions);
|
||||
await this.sidebar.getByText("Assets").click(clickOptions);
|
||||
}
|
||||
|
||||
async clickLibraries(clickOptions = {}) {
|
||||
await this.libraries.click(clickOptions);
|
||||
async openLibrariesModal(clickOptions = {}) {
|
||||
await this.sidebar.getByText("Libraries").click(clickOptions);
|
||||
}
|
||||
|
||||
async clickLibrary(name, clickOptions = {}) {
|
||||
|
@ -136,11 +157,15 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||
.getByTestId("library-item")
|
||||
.filter({ hasText: name })
|
||||
.getByRole("button")
|
||||
.click(clickOptions);
|
||||
.click(clickOptions);
|
||||
}
|
||||
|
||||
async clickCloseLibraries(clickOptions = {}) {
|
||||
await this.closeLibraries.click(clickOptions);
|
||||
async closeLibrariesModal(clickOptions = {}) {
|
||||
await this.librariesModal.getByRole("button", { name: "Close" }).click(clickOptions);
|
||||
}
|
||||
|
||||
async clickColorPalette(clickOptions = {}) {
|
||||
await this.palette.getByRole("button", { name: "Color Palette (Alt+P)" }).click(clickOptions);
|
||||
}
|
||||
|
||||
async clickColorPalette(clickOptions = {}) {
|
||||
|
|
|
@ -3,6 +3,11 @@ import DashboardPage from "../pages/DashboardPage";
|
|||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await DashboardPage.init(page);
|
||||
await DashboardPage.mockRPC(
|
||||
page,
|
||||
"get-profile",
|
||||
"logged-in-user/get-profile-logged-in-no-onboarding.json",
|
||||
);
|
||||
});
|
||||
|
||||
test("Dashboad page has title ", async ({ page }) => {
|
||||
|
|
|
@ -7,6 +7,8 @@ test.beforeEach(async ({ page }) => {
|
|||
|
||||
const multipleConstraintsFileId = `03bff843-920f-81a1-8004-756365e1eb6a`;
|
||||
const multipleConstraintsPageId = `03bff843-920f-81a1-8004-756365e1eb6b`;
|
||||
const multipleAttributesFileId = `1795a568-0df0-8095-8004-7ba741f56be2`;
|
||||
const multipleAttributesPageId = `1795a568-0df0-8095-8004-7ba741f56be3`;
|
||||
|
||||
const setupFileWithMultipeConstraints = async (workspace) => {
|
||||
await workspace.setupEmptyFile();
|
||||
|
@ -21,6 +23,15 @@ const setupFileWithMultipeConstraints = async (workspace) => {
|
|||
);
|
||||
};
|
||||
|
||||
const setupFileWithMultipeAttributes = async (workspace) => {
|
||||
await workspace.setupEmptyFile();
|
||||
await workspace.mockRPC(/get\-file\?/, "design/get-file-multiple-attributes.json");
|
||||
await workspace.mockRPC(
|
||||
"get-file-object-thumbnails?file-id=*",
|
||||
"design/get-file-object-thumbnails-multiple-attributes.json",
|
||||
);
|
||||
};
|
||||
|
||||
test.describe("Constraints", () => {
|
||||
test("Constraint dropdown shows 'Mixed' when multiple layers are selected with different constraints", async ({
|
||||
page,
|
||||
|
@ -45,6 +56,43 @@ test.describe("Constraints", () => {
|
|||
});
|
||||
});
|
||||
|
||||
test.describe("Multiple shapes attributes", () => {
|
||||
test("User selects multiple shapes with sames fills, strokes, shadows and blur", async ({ page }) => {
|
||||
const workspace = new WorkspacePage(page);
|
||||
await setupFileWithMultipeConstraints(workspace);
|
||||
await workspace.goToWorkspace({
|
||||
fileId: multipleConstraintsFileId,
|
||||
pageId: multipleConstraintsPageId,
|
||||
});
|
||||
|
||||
await workspace.clickToggableLayer("Board");
|
||||
await workspace.clickLeafLayer("Ellipse");
|
||||
await workspace.clickLeafLayer("Rectangle", { modifiers: ["Shift"] });
|
||||
|
||||
await expect(workspace.page.getByTestId("add-fill")).toBeVisible();
|
||||
await expect(workspace.page.getByTestId("add-stroke")).toBeVisible();
|
||||
await expect(workspace.page.getByTestId("add-shadow")).toBeVisible();
|
||||
await expect(workspace.page.getByTestId("add-blur")).toBeVisible();
|
||||
});
|
||||
|
||||
test("User selects multiple shapes with different fills, strokes, shadows and blur", async ({ page }) => {
|
||||
const workspace = new WorkspacePage(page);
|
||||
await setupFileWithMultipeAttributes(workspace);
|
||||
await workspace.goToWorkspace({
|
||||
fileId: multipleAttributesFileId,
|
||||
pageId: multipleAttributesPageId,
|
||||
});
|
||||
|
||||
await workspace.clickLeafLayer("Ellipse");
|
||||
await workspace.clickLeafLayer("Rectangle", { modifiers: ["Shift"] });
|
||||
|
||||
await expect(workspace.page.getByTestId("add-fill")).toBeHidden();
|
||||
await expect(workspace.page.getByTestId("add-stroke")).toBeHidden();
|
||||
await expect(workspace.page.getByTestId("add-shadow")).toBeHidden();
|
||||
await expect(workspace.page.getByTestId("add-blur")).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test("BUG 7760 - Layout losing properties when changing parents", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
|
|
32
frontend/playwright/ui/specs/onboarding.spec.js
Normal file
32
frontend/playwright/ui/specs/onboarding.spec.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import DashboardPage from "../pages/DashboardPage";
|
||||
import OnboardingPage from "../pages/OnboardingPage"
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await DashboardPage.init(page);
|
||||
await DashboardPage.mockRPC(page, "get-profile", "logged-in-user/get-profile-logged-in.json");
|
||||
});
|
||||
|
||||
|
||||
test("User can complete the onboarding", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
const onboardingPage = new OnboardingPage(page);
|
||||
|
||||
await dashboardPage.goToWorkspace();
|
||||
await expect(page.getByRole("heading", { name: "Help us get to know you" })).toBeVisible();
|
||||
|
||||
await onboardingPage.fillOnboardingInputsStep1();
|
||||
await expect(page.getByRole("heading", { name: "Which one of these tools do" })).toBeVisible();
|
||||
|
||||
await onboardingPage.fillOnboardingInputsStep2();
|
||||
await expect(page.getByRole("heading", { name: "Tell us about your job" })).toBeVisible();
|
||||
|
||||
await onboardingPage.fillOnboardingInputsStep3();
|
||||
await expect(page.getByRole("heading", { name: "Where would you like to get" })).toBeVisible();
|
||||
|
||||
await onboardingPage.fillOnboardingInputsStep4();
|
||||
await expect(page.getByRole("heading", { name: "How did you hear about Penpot?" })).toBeVisible();
|
||||
|
||||
await onboardingPage.fillOnboardingInputsStep5();
|
||||
await expect(page.getByRole("button", { name: "Start" })).toBeEnabled();
|
||||
});
|
56
frontend/playwright/ui/specs/sidebar.spec.js
Normal file
56
frontend/playwright/ui/specs/sidebar.spec.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "../pages/WorkspacePage";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WorkspacePage.init(page);
|
||||
});
|
||||
|
||||
test.describe("Layers tab", () => {
|
||||
test("BUG 7466 - Layers tab height extends to the bottom when 'Pages' is collapsed", async ({ page }) => {
|
||||
const workspace = new WorkspacePage(page);
|
||||
await workspace.setupEmptyFile();
|
||||
|
||||
await workspace.goToWorkspace();
|
||||
|
||||
const { height: heightExpanded } = await workspace.layers.boundingBox();
|
||||
await workspace.togglePages();
|
||||
const { height: heightCollapsed } = await workspace.layers.boundingBox();
|
||||
|
||||
expect(heightExpanded > heightCollapsed);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Assets tab", () => {
|
||||
test("User adds a library and its automatically selected in the color palette", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockRPC("link-file-to-library", "workspace/link-file-to-library.json");
|
||||
await workspacePage.mockRPC("unlink-file-from-library", "workspace/unlink-file-from-library.json");
|
||||
await workspacePage.mockRPC(
|
||||
"get-team-shared-files?team-id=*",
|
||||
"workspace/get-team-shared-libraries-non-empty.json",
|
||||
);
|
||||
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
// Add Testing library 1
|
||||
await workspacePage.clickColorPalette();
|
||||
await workspacePage.clickAssets();
|
||||
// Now the get-file call should return a library
|
||||
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
|
||||
await workspacePage.openLibrariesModal();
|
||||
await workspacePage.clickLibrary("Testing library 1");
|
||||
await workspacePage.closeLibrariesModal();
|
||||
|
||||
await expect(workspacePage.palette.getByRole("button", { name: "test-color-187cd5" })).toBeVisible();
|
||||
|
||||
// Remove Testing library 1
|
||||
await workspacePage.openLibrariesModal();
|
||||
await workspacePage.clickLibrary("Testing library 1");
|
||||
await workspacePage.closeLibrariesModal();
|
||||
|
||||
await expect(
|
||||
workspacePage.palette.getByText("There are no color styles in your library yet"),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
|
@ -39,6 +39,60 @@ test("User draws a rect", async ({ page }) => {
|
|||
await expect(shape).toHaveAttribute("height", "100");
|
||||
});
|
||||
|
||||
test("User makes a group", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-not-empty.json");
|
||||
await workspacePage.mockRPC("update-file?id=*", "workspace/update-file-create-rect.json");
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "6191cd35-bb1f-81f7-8004-7cc63d087374",
|
||||
pageId: "6191cd35-bb1f-81f7-8004-7cc63d087375",
|
||||
});
|
||||
await workspacePage.clickLeafLayer("Rectangle");
|
||||
await workspacePage.page.keyboard.press("ControlOrMeta+g");
|
||||
await workspacePage.expectSelectedLayer("Group");
|
||||
});
|
||||
|
||||
test("Bug 7654 - Toolbar keeps toggling on and off on spacebar press", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.goToWorkspace();
|
||||
|
||||
await workspacePage.toggleToolbarButton.click();
|
||||
await workspacePage.page.keyboard.press("Backspace");
|
||||
await workspacePage.page.keyboard.press("Enter");
|
||||
await workspacePage.expectHiddenToolbarOptions();
|
||||
});
|
||||
|
||||
test("Bug 7525 - User moves a scrollbar and no selciont rectangle appears", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-not-empty.json");
|
||||
await workspacePage.mockRPC("update-file?id=*", "workspace/update-file-create-rect.json");
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "6191cd35-bb1f-81f7-8004-7cc63d087374",
|
||||
pageId: "6191cd35-bb1f-81f7-8004-7cc63d087375",
|
||||
});
|
||||
|
||||
// Move created rect to a corner, in orther to get scrollbars
|
||||
await workspacePage.panOnViewportAt(128, 128, 300, 300);
|
||||
|
||||
// Check scrollbars appear
|
||||
const horizontalScrollbar = workspacePage.horizontalScrollbar;
|
||||
await expect(horizontalScrollbar).toBeVisible();
|
||||
|
||||
// Grab scrollbar and move
|
||||
const {x, y} = await horizontalScrollbar.boundingBox();
|
||||
await page.waitForTimeout(100);
|
||||
await workspacePage.viewport.hover({ position: { x: x, y: y + 5 } });
|
||||
await page.mouse.down();
|
||||
await workspacePage.viewport.hover({ position: { x: x - 130, y: y - 95 } });
|
||||
|
||||
await expect(workspacePage.selectionRect).not.toBeInViewport();
|
||||
});
|
||||
|
||||
test("User adds a library and its automatically selected in the color palette", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
|
@ -53,31 +107,16 @@ test("User adds a library and its automatically selected in the color palette",
|
|||
await workspacePage.clickAssets();
|
||||
// Now the get-file call should return a library
|
||||
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
|
||||
await workspacePage.clickLibraries();
|
||||
await workspacePage.openLibrariesModal();
|
||||
await workspacePage.clickLibrary("Testing library 1")
|
||||
await workspacePage.clickCloseLibraries();
|
||||
await workspacePage.closeLibrariesModal();
|
||||
|
||||
await expect(workspacePage.palette.getByRole("button", { name: "test-color-187cd5" })).toBeVisible();
|
||||
|
||||
// Remove Testing library 1
|
||||
await workspacePage.clickLibraries();
|
||||
await workspacePage.openLibrariesModal();
|
||||
await workspacePage.clickLibrary("Testing library 1")
|
||||
await workspacePage.clickCloseLibraries();
|
||||
await workspacePage.closeLibrariesModal();
|
||||
|
||||
await expect(workspacePage.palette.getByText('There are no color styles in your library yet')).toBeVisible();
|
||||
});
|
||||
|
||||
test("User makes a group", async ({ page }) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-not-empty.json");
|
||||
await workspacePage.mockRPC("update-file?id=*", "workspace/update-file-create-rect.json");
|
||||
|
||||
await workspacePage.goToWorkspace({
|
||||
fileId: "6191cd35-bb1f-81f7-8004-7cc63d087374",
|
||||
pageId: "6191cd35-bb1f-81f7-8004-7cc63d087375"
|
||||
});
|
||||
await workspacePage.clickLeafLayer("Rectangle");
|
||||
await workspacePage.page.keyboard.press("ControlOrMeta+g");
|
||||
await workspacePage.expectSelectedLayer("Group");
|
||||
});
|
||||
|
|
|
@ -328,11 +328,15 @@
|
|||
(-data [_] {})
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rp/cmd! :logout)
|
||||
(rx/delay-at-least 300)
|
||||
(rx/catch (constantly (rx/of 1)))
|
||||
(rx/map #(logged-out params)))))))
|
||||
(watch [_ state _]
|
||||
(let [profile-id (:profile-id state)]
|
||||
(->> (rx/interval 500)
|
||||
(rx/take 1)
|
||||
(rx/mapcat (fn [_]
|
||||
(->> (rp/cmd! :logout {:profile-id profile-id})
|
||||
(rx/delay-at-least 300)
|
||||
(rx/catch (constantly (rx/of 1))))))
|
||||
(rx/map #(logged-out params))))))))
|
||||
|
||||
;; --- Update Profile
|
||||
|
||||
|
|
|
@ -248,7 +248,7 @@
|
|||
(assoc :stroke-style :solid)
|
||||
|
||||
(not (contains? new-attrs :stroke-alignment))
|
||||
(assoc :stroke-alignment :inner)
|
||||
(assoc :stroke-alignment :center)
|
||||
|
||||
:always
|
||||
(d/without-nils))]
|
||||
|
|
|
@ -198,7 +198,8 @@
|
|||
(dws/select-shapes (d/ordered-set (:id group))))
|
||||
(ptk/data-event :layout/update {:ids parents}))))))))
|
||||
|
||||
(def group-selected
|
||||
(defn group-selected
|
||||
[]
|
||||
(ptk/reify ::group-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -258,7 +259,8 @@
|
|||
(when change-selection?
|
||||
(dws/select-shapes child-ids))))))))
|
||||
|
||||
(def ungroup-selected
|
||||
(defn ungroup-selected
|
||||
[]
|
||||
(ptk/reify ::ungroup-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
|
|
@ -79,20 +79,21 @@
|
|||
(rx/from (->> guides (mapv #(remove-guide %))))))))
|
||||
|
||||
(defmethod ptk/resolve ::move-frame-guides
|
||||
[_ ids]
|
||||
[_ args]
|
||||
(dm/assert!
|
||||
"expected a coll of uuids"
|
||||
(every? uuid? ids))
|
||||
(every? uuid? (:ids args)))
|
||||
(ptk/reify ::move-frame-guides
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [objects (wsh/lookup-page-objects state)
|
||||
(let [ids (:ids args)
|
||||
object-modifiers (:modifiers args)
|
||||
|
||||
objects (wsh/lookup-page-objects state)
|
||||
|
||||
is-frame? (fn [id] (= :frame (get-in objects [id :type])))
|
||||
frame-ids? (into #{} (filter is-frame?) ids)
|
||||
|
||||
object-modifiers (get state :workspace-modifiers)
|
||||
|
||||
build-move-event
|
||||
(fn [guide]
|
||||
(let [frame (get objects (:frame-id guide))
|
||||
|
|
|
@ -497,7 +497,7 @@
|
|||
(if undo-transation?
|
||||
(rx/of (dwu/start-undo-transaction undo-id))
|
||||
(rx/empty))
|
||||
(rx/of (ptk/event ::dwg/move-frame-guides ids-with-children)
|
||||
(rx/of (ptk/event ::dwg/move-frame-guides {:ids ids-with-children :modifiers object-modifiers})
|
||||
(ptk/event ::dwcm/move-frame-comment-threads ids-with-children)
|
||||
(dwsh/update-shapes
|
||||
ids
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
(or (= type ::common/finish-path)
|
||||
(= type :app.main.data.workspace.path.shortcuts/esc-pressed)
|
||||
(= type :app.main.data.workspace.common/clear-edition-mode)
|
||||
(= type :app.main.data.workspace.edition/clear-edition-mode)
|
||||
(= type :app.main.data.workspace/finalize-page)
|
||||
(= event :interrupt) ;; ESC
|
||||
(and ^boolean (mse/mouse-event? event)
|
||||
|
|
|
@ -119,12 +119,12 @@
|
|||
:group {:tooltip (ds/meta "G")
|
||||
:command (ds/c-mod "g")
|
||||
:subsections [:modify-layers]
|
||||
:fn #(emit-when-no-readonly dw/group-selected)}
|
||||
:fn #(emit-when-no-readonly (dw/group-selected))}
|
||||
|
||||
:ungroup {:tooltip (ds/shift "G")
|
||||
:command "shift+g"
|
||||
:subsections [:modify-layers]
|
||||
:fn #(emit-when-no-readonly dw/ungroup-selected)}
|
||||
:fn #(emit-when-no-readonly (dw/ungroup-selected))}
|
||||
|
||||
:mask {:tooltip (ds/meta "M")
|
||||
:command (ds/c-mod "m")
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
[app.main.ui.icons :as i]
|
||||
[app.util.i18n :refer [tr tr-html]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :as sto]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[rumext.v2 :as mf]))
|
||||
|
@ -163,11 +164,7 @@
|
|||
|
||||
;; --- PAGE: register validation
|
||||
|
||||
(defn- handle-register-error
|
||||
[_form _data]
|
||||
(st/emit! (msg/error (tr "errors.generic"))))
|
||||
|
||||
(defn- handle-register-success
|
||||
(defn- on-register-success
|
||||
[data]
|
||||
(cond
|
||||
(some? (:invitation-token data))
|
||||
|
@ -178,7 +175,9 @@
|
|||
(st/emit! (du/login-from-register))
|
||||
|
||||
:else
|
||||
(st/emit! (rt/nav :auth-register-success {} {:email (:email data)}))))
|
||||
(do
|
||||
(swap! sto/storage assoc ::email (:email data))
|
||||
(st/emit! (rt/nav :auth-register-success)))))
|
||||
|
||||
(s/def ::accept-terms-and-privacy (s/and ::us/boolean true?))
|
||||
(s/def ::accept-newsletter-subscription ::us/boolean)
|
||||
|
@ -192,31 +191,63 @@
|
|||
:opt-un [::accept-terms-and-privacy
|
||||
::accept-newsletter-subscription])))
|
||||
|
||||
(mf/defc terms-and-privacy
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[]
|
||||
(let [terms-label
|
||||
(mf/html
|
||||
[:& tr-html
|
||||
{:tag-name "div"
|
||||
:label "auth.terms-and-privacy-agreement"
|
||||
:params [cf/terms-of-service-uri cf/privacy-policy-uri]}])]
|
||||
|
||||
[:div {:class (stl/css :fields-row :input-visible :accept-terms-and-privacy-wrapper)}
|
||||
[:& fm/input {:name :accept-terms-and-privacy
|
||||
:class (stl/css :checkbox-terms-and-privacy)
|
||||
:type "checkbox"
|
||||
:default-checked false
|
||||
:label terms-label}]]))
|
||||
|
||||
(mf/defc register-validate-form
|
||||
{::mf/props :obj}
|
||||
[{:keys [params on-success-callback]}]
|
||||
(let [form (fm/use-form :spec ::register-validate-form
|
||||
:validators [(fm/validate-not-empty :fullname (tr "auth.name.not-all-space"))
|
||||
(fm/validate-length :fullname fm/max-length-allowed (tr "auth.name.too-long"))]
|
||||
(let [validators (mf/with-memo []
|
||||
[(fm/validate-not-empty :fullname (tr "auth.name.not-all-space"))
|
||||
(fm/validate-length :fullname fm/max-length-allowed (tr "auth.name.too-long"))])
|
||||
|
||||
form (fm/use-form :spec ::register-validate-form
|
||||
:validators validators
|
||||
:initial params)
|
||||
|
||||
submitted? (mf/use-state false)
|
||||
|
||||
on-success (fn [p]
|
||||
(if (nil? on-success-callback)
|
||||
(handle-register-success p)
|
||||
(on-success-callback (:email p))))
|
||||
on-success
|
||||
(mf/use-fn
|
||||
(mf/deps on-success-callback)
|
||||
(fn [params]
|
||||
(if (nil? on-success-callback)
|
||||
(on-register-success params)
|
||||
(on-success-callback (:email params)))))
|
||||
|
||||
on-error
|
||||
(mf/use-fn
|
||||
(fn [_cause]
|
||||
(st/emit! (msg/error (tr "errors.generic")))))
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(fn [form _event]
|
||||
(fn [form _]
|
||||
(reset! submitted? true)
|
||||
(let [params (:clean-data @form)]
|
||||
(->> (rp/cmd! :register-profile params)
|
||||
(rx/finalize #(reset! submitted? false))
|
||||
(rx/subs! on-success
|
||||
(partial handle-register-error form))))))]
|
||||
(rx/subs! on-success on-error)))))]
|
||||
|
||||
[:& fm/form {:on-submit on-submit :form form
|
||||
[:& fm/form {:on-submit on-submit
|
||||
:form form
|
||||
:class (stl/css :register-validate-form)}
|
||||
|
||||
[:div {:class (stl/css :fields-row)}
|
||||
[:& fm/input {:name :fullname
|
||||
:label (tr "auth.fullname")
|
||||
|
@ -225,18 +256,7 @@
|
|||
:class (stl/css :form-field)}]]
|
||||
|
||||
(when (contains? cf/flags :terms-and-privacy-checkbox)
|
||||
(let [terms-label
|
||||
(mf/html
|
||||
[:& tr-html
|
||||
{:tag-name "div"
|
||||
:label "auth.terms-and-privacy-agreement"
|
||||
:params [cf/terms-of-service-uri cf/privacy-policy-uri]}])]
|
||||
[:div {:class (stl/css :fields-row :input-visible :accept-terms-and-privacy-wrapper)}
|
||||
[:& fm/input {:name :accept-terms-and-privacy
|
||||
:class (stl/css :checkbox-terms-and-privacy)
|
||||
:type "checkbox"
|
||||
:default-checked false
|
||||
:label terms-label}]]))
|
||||
[:& terms-and-privacy])
|
||||
|
||||
[:> fm/submit-button*
|
||||
{:label (tr "auth.register-submit")
|
||||
|
@ -245,6 +265,7 @@
|
|||
|
||||
|
||||
(mf/defc register-validate-page
|
||||
{::mf/props :obj}
|
||||
[{:keys [params]}]
|
||||
[:div {:class (stl/css :auth-form-wrapper)}
|
||||
[:h1 {:class (stl/css :logo-container)}
|
||||
|
@ -263,13 +284,15 @@
|
|||
(tr "labels.go-back")]]]])
|
||||
|
||||
(mf/defc register-success-page
|
||||
[{:keys [params]}]
|
||||
[:div {:class (stl/css :auth-form-wrapper :register-success)}
|
||||
[:h1 {:class (stl/css :logo-container)}
|
||||
[:a {:href "#/" :title "Penpot" :class (stl/css :logo-btn)} i/logo]]
|
||||
[:div {:class (stl/css :auth-title-wrapper)}
|
||||
[:h2 {:class (stl/css :auth-title)}
|
||||
(tr "auth.check-mail")]
|
||||
[:div {:class (stl/css :notification-text)} (tr "auth.verification-email-sent")]]
|
||||
[:div {:class (stl/css :notification-text-email)} (:email params "")]
|
||||
[:div {:class (stl/css :notification-text)} (tr "auth.check-your-email")]])
|
||||
{::mf/props :obj}
|
||||
[]
|
||||
(let [email (::email @sto/storage)]
|
||||
[:div {:class (stl/css :auth-form-wrapper :register-success)}
|
||||
[:h1 {:class (stl/css :logo-container)}
|
||||
[:a {:href "#/" :title "Penpot" :class (stl/css :logo-btn)} i/logo]]
|
||||
[:div {:class (stl/css :auth-title-wrapper)}
|
||||
[:h2 {:class (stl/css :auth-title)}
|
||||
(tr "auth.check-mail")]
|
||||
[:div {:class (stl/css :notification-text)} (tr "auth.verification-email-sent")]]
|
||||
[:div {:class (stl/css :notification-text-email)} email]
|
||||
[:div {:class (stl/css :notification-text)} (tr "auth.check-your-email")]]))
|
||||
|
|
|
@ -142,11 +142,10 @@
|
|||
// thread-content
|
||||
.thread-content {
|
||||
position: absolute;
|
||||
overflow-y: scroll;
|
||||
scrollbar-gutter: stable;
|
||||
overflow-y: auto;
|
||||
width: $s-284;
|
||||
padding: $s-12;
|
||||
padding-inline-end: 0;
|
||||
padding-inline-end: $s-8;
|
||||
|
||||
pointer-events: auto;
|
||||
user-select: text;
|
||||
|
|
|
@ -44,9 +44,17 @@
|
|||
(or (empty? overlays-ids) (nil? shape) (cfh/root? shape)) base-frame
|
||||
:else (find-relative-to-base-frame (cfh/get-parent objects (:id shape)) objects overlays-ids base-frame)))
|
||||
|
||||
(defn- ignore-frame-shape
|
||||
[shape objects manual?]
|
||||
(let [shape (cond-> shape ;; When the the interaction is not manual and its origin is a frame,
|
||||
;; we need to ignore it on all the find-frame calculations
|
||||
(and (:frame-id shape) (not manual?))
|
||||
(assoc :type :rect))
|
||||
objects (assoc objects (:id shape) shape)]
|
||||
[shape objects]))
|
||||
|
||||
(defn- activate-interaction
|
||||
[interaction shape base-frame frame-offset objects overlays]
|
||||
|
||||
(case (:action-type interaction)
|
||||
:navigate
|
||||
(when-let [frame-id (:destination interaction)]
|
||||
|
@ -58,9 +66,11 @@
|
|||
(dv/go-to-frame frame-id (:animation interaction)))))
|
||||
|
||||
:open-overlay
|
||||
(let [dest-frame-id (:destination interaction)
|
||||
(let [manual? (= :manual (:overlay-pos-type interaction))
|
||||
[shape objects] (ignore-frame-shape shape objects manual?)
|
||||
dest-frame-id (:destination interaction)
|
||||
dest-frame (get objects dest-frame-id)
|
||||
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
||||
relative-to-id (if manual?
|
||||
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
||||
(:frame-id shape)
|
||||
(:id shape))
|
||||
|
@ -88,7 +98,9 @@
|
|||
fixed-base?))))
|
||||
|
||||
:toggle-overlay
|
||||
(let [dest-frame-id (:destination interaction)
|
||||
(let [manual? (= :manual (:overlay-pos-type interaction))
|
||||
[shape objects] (ignore-frame-shape shape objects manual?)
|
||||
dest-frame-id (:destination interaction)
|
||||
dest-frame (get objects dest-frame-id)
|
||||
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
||||
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
||||
|
@ -146,7 +158,9 @@
|
|||
(st/emit! (dv/close-overlay frame-id)))
|
||||
|
||||
:toggle-overlay
|
||||
(let [dest-frame-id (:destination interaction)
|
||||
(let [manual? (= :manual (:overlay-pos-type interaction))
|
||||
[shape objects] (ignore-frame-shape shape objects manual?)
|
||||
dest-frame-id (:destination interaction)
|
||||
dest-frame (get objects dest-frame-id)
|
||||
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
||||
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
||||
|
@ -178,7 +192,9 @@
|
|||
|
||||
|
||||
:close-overlay
|
||||
(let [dest-frame-id (:destination interaction)
|
||||
(let [manual? (= :manual (:overlay-pos-type interaction))
|
||||
[shape objects] (ignore-frame-shape shape objects manual?)
|
||||
dest-frame-id (:destination interaction)
|
||||
dest-frame (get objects dest-frame-id)
|
||||
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
||||
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
||||
|
|
|
@ -243,8 +243,8 @@
|
|||
is-group? (and single? has-group?)
|
||||
is-bool? (and single? has-bool?)
|
||||
|
||||
do-create-group #(st/emit! dw/group-selected)
|
||||
do-remove-group #(st/emit! dw/ungroup-selected)
|
||||
do-create-group #(st/emit! (dw/group-selected))
|
||||
do-remove-group #(st/emit! (dw/ungroup-selected))
|
||||
do-mask-group #(st/emit! (dw/mask-group))
|
||||
do-unmask-group #(st/emit! (dw/unmask-group))
|
||||
do-create-artboard-from-selection
|
||||
|
|
|
@ -519,6 +519,7 @@
|
|||
[:div {:class (stl/css :modal-dialog)}
|
||||
[:button {:class (stl/css :close-btn)
|
||||
:on-click close-dialog
|
||||
:aria-label (tr "labels.close")
|
||||
:data-testid "close-libraries"}
|
||||
close-icon]
|
||||
[:div {:class (stl/css :modal-title)}
|
||||
|
|
|
@ -38,8 +38,7 @@
|
|||
(let [options-mode (mf/deref refs/options-mode-global)
|
||||
mode-inspect? (= options-mode :inspect)
|
||||
project (mf/deref refs/workspace-project)
|
||||
show-pages? (mf/use-state true)
|
||||
toggle-pages (mf/use-callback #(reset! show-pages? not))
|
||||
|
||||
|
||||
section (cond (or mode-inspect? (contains? layout :layers)) :layers
|
||||
(contains? layout :assets) :assets)
|
||||
|
@ -50,9 +49,12 @@
|
|||
{on-pointer-down :on-pointer-down on-lost-pointer-capture :on-lost-pointer-capture on-pointer-move :on-pointer-move parent-ref :parent-ref size :size}
|
||||
(use-resize-hook :left-sidebar 275 275 500 :x false :left)
|
||||
|
||||
{on-pointer-down-pages :on-pointer-down on-lost-pointer-capture-pages :on-lost-pointer-capture on-pointer-move-pages :on-pointer-move size-pages :size}
|
||||
{on-pointer-down-pages :on-pointer-down on-lost-pointer-capture-pages :on-lost-pointer-capture on-pointer-move-pages :on-pointer-move size-pages-opened :size}
|
||||
(use-resize-hook :sitemap 200 38 400 :y false nil)
|
||||
|
||||
show-pages? (mf/use-state true)
|
||||
toggle-pages (mf/use-callback #(reset! show-pages? not))
|
||||
size-pages (mf/use-memo (mf/deps show-pages? size-pages-opened) (fn [] (if @show-pages? size-pages-opened 32)))
|
||||
|
||||
handle-collapse
|
||||
(mf/use-fn #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
||||
|
@ -63,6 +65,7 @@
|
|||
[:& (mf/provider muc/sidebar) {:value :left}
|
||||
[:aside {:ref parent-ref
|
||||
:id "left-sidebar-aside"
|
||||
:data-testid "left-sidebar"
|
||||
:data-size (str size)
|
||||
:class (stl/css-case :left-settings-bar true
|
||||
:global/two-row (<= size 300)
|
||||
|
|
|
@ -84,10 +84,8 @@ $width-settings-bar-max: $s-500;
|
|||
|
||||
.resize-area-horiz {
|
||||
position: absolute;
|
||||
// top: calc($s-88 + var(--height, 200px));
|
||||
left: 0;
|
||||
width: 100%;
|
||||
// height: $s-8;
|
||||
border-bottom: $s-2 solid var(--resize-area-border-color);
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
|
|
@ -510,7 +510,7 @@
|
|||
(mf/use-fn
|
||||
#(st/emit! (dw/toggle-focus-mode)))]
|
||||
|
||||
[:div#layers {:class (stl/css :layers) :data-testid "layers"}
|
||||
[:div#layers {:class (stl/css :layers) :data-testid "layer-tree"}
|
||||
(if (d/not-empty? focus)
|
||||
[:div {:class (stl/css :tool-window-bar)}
|
||||
[:button {:class (stl/css :focus-title)
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
:class (stl/css-case :title-spacing-blur (not has-value?))}
|
||||
(when-not has-value?
|
||||
[:button {:class (stl/css :add-blur)
|
||||
:data-testid "add-blur"
|
||||
:on-click handle-add} i/add])]]
|
||||
(when (and open? has-value?)
|
||||
[:div {:class (stl/css :element-set-content)}
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
(mf/deps adjust-textarea-size creating?)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(rerender-fn)
|
||||
(when-let [textarea (mf/ref-val textarea-ref)]
|
||||
(dom/set-value! textarea annotation)
|
||||
(reset! editing* false)
|
||||
|
@ -98,6 +99,7 @@
|
|||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(rerender-fn)
|
||||
(when ^boolean main-instance?
|
||||
(when-let [textarea (mf/ref-val textarea-ref)]
|
||||
(reset! editing* true)
|
||||
|
@ -109,6 +111,7 @@
|
|||
(mf/deps creating?)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(rerender-fn)
|
||||
(when-let [textarea (mf/ref-val textarea-ref)]
|
||||
(let [text (dom/get-value textarea)]
|
||||
(when-not (str/blank? text)
|
||||
|
@ -124,6 +127,7 @@
|
|||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [on-accept (fn []
|
||||
(rerender-fn)
|
||||
(st/emit!
|
||||
;; (ptk/data-event {::ev/name "delete-component-annotation"})
|
||||
(when creating?
|
||||
|
|
|
@ -146,6 +146,7 @@
|
|||
|
||||
(when (and (not disable-remove?) (not (= :multiple fills)))
|
||||
[:button {:class (stl/css :add-fill)
|
||||
:data-testid "add-fill"
|
||||
:on-click on-add} i/add])]]
|
||||
|
||||
(when open?
|
||||
|
|
|
@ -298,6 +298,7 @@
|
|||
|
||||
(when-not (= :multiple shadows)
|
||||
[:button {:class (stl/css :add-shadow)
|
||||
:data-testid "add-shadow"
|
||||
:on-click on-add-shadow} i/add])]]
|
||||
|
||||
(when open?
|
||||
|
|
|
@ -169,9 +169,10 @@
|
|||
:on-collapsed toggle-content
|
||||
:title label
|
||||
:class (stl/css-case :title-spacing-stroke (not has-strokes?))}
|
||||
|
||||
[:button {:class (stl/css :add-stroke)
|
||||
:on-click on-add-stroke} i/add]]]
|
||||
(when (not (= :multiple strokes))
|
||||
[:button {:class (stl/css :add-stroke)
|
||||
:data-testid "add-stroke"
|
||||
:on-click on-add-stroke} i/add])]]
|
||||
(when open?
|
||||
[:div {:class (stl/css-case :element-content true
|
||||
:empty-content (not has-strokes?))}
|
||||
|
|
|
@ -205,7 +205,6 @@
|
|||
(fn [event]
|
||||
(st/emit! (dw/create-page {:file-id file-id :project-id project-id}))
|
||||
(-> event dom/get-current-target dom/blur!)))
|
||||
size (if show-pages? size 32)
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)]
|
||||
|
||||
[:div {:class (stl/css :sitemap)
|
||||
|
|
|
@ -115,13 +115,16 @@
|
|||
|
||||
toggle-toolbar
|
||||
(mf/use-fn
|
||||
#(st/emit! (dwc/toggle-toolbar-visibility)))]
|
||||
(fn [event]
|
||||
(dom/blur! (dom/get-target event))
|
||||
(st/emit! (dwc/toggle-toolbar-visibility))))]
|
||||
|
||||
(when-not ^boolean read-only?
|
||||
[:aside {:class (stl/css-case :main-toolbar true
|
||||
:main-toolbar-no-rulers (not rulers?)
|
||||
:main-toolbar-hidden hide-toolbar?)}
|
||||
[:ul {:class (stl/css :main-toolbar-options)}
|
||||
[:ul {:class (stl/css :main-toolbar-options)
|
||||
:data-testid "toolbar-options"}
|
||||
[:li
|
||||
[:button
|
||||
{:title (tr "workspace.toolbar.move" (sc/get-tooltip :move))
|
||||
|
@ -197,7 +200,9 @@
|
|||
:on-click toggle-debug-panel}
|
||||
i/bug]])]]
|
||||
|
||||
[:button {:class (stl/css :toolbar-handler)
|
||||
[:button {:title (tr "workspace.toolbar.toggle-toolbar")
|
||||
:aria-label (tr "workspace.toolbar.toggle-toolbar")
|
||||
:class (stl/css :toolbar-handler)
|
||||
:on-click toggle-toolbar}
|
||||
[:div {:class (stl/css :toolbar-handler-btn)}]]])))
|
||||
|
||||
|
|
|
@ -636,8 +636,8 @@
|
|||
:objects base-objects
|
||||
:modifiers modifiers
|
||||
:shape frame
|
||||
:view-only true}]))
|
||||
|
||||
:view-only true}]))]
|
||||
[:g.scrollbar-wrapper {:clipPath "url(#clip-handlers)"}
|
||||
[:& scroll-bars/viewport-scrollbars
|
||||
{:objects base-objects
|
||||
:zoom zoom
|
||||
|
|
|
@ -196,7 +196,8 @@
|
|||
|
||||
[:*
|
||||
(when show-v-scroll?
|
||||
[:g.v-scroll {:fill clr/black}
|
||||
[:g.v-scroll {:fill clr/black
|
||||
:data-testid "vertical-scrollbar"}
|
||||
[:rect {:on-pointer-move #(on-pointer-move % :y)
|
||||
:on-pointer-down #(on-pointer-down % :y)
|
||||
:on-pointer-up on-pointer-up
|
||||
|
@ -210,7 +211,8 @@
|
|||
:style {:stroke "white"
|
||||
:stroke-width (/ 0.15 zoom)}}]])
|
||||
(when show-h-scroll?
|
||||
[:g.h-scroll {:fill clr/black}
|
||||
[:g.h-scroll {:fill clr/black
|
||||
:data-testid "horizontal-scrollbar"}
|
||||
[:rect {:on-pointer-move #(on-pointer-move % :x)
|
||||
:on-pointer-down #(on-pointer-down % :x)
|
||||
:on-pointer-up on-pointer-up
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
[:rect.selection-rect
|
||||
{:x (:x data)
|
||||
:y (:y data)
|
||||
:data-testid "workspace-selection-rect"
|
||||
:width (:width data)
|
||||
:height (:height data)
|
||||
:style {;; Primary with 0.1 opacity
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.state :as ths]))
|
||||
|
@ -46,3 +47,36 @@
|
|||
(t/is (= (count fills') 1))
|
||||
(t/is (= (:fill-color fill') "#fabada"))
|
||||
(t/is (= (:fill-opacity fill') 1))))))))
|
||||
|
||||
(t/deftest test-update-stroke
|
||||
;; Old shapes without stroke-alignment are rendered as if it is centered
|
||||
(t/async
|
||||
done
|
||||
(let [;; ==== Setup
|
||||
store
|
||||
(ths/setup-store
|
||||
(-> (cthf/sample-file :file1 :page-label :page1)
|
||||
(cths/add-sample-shape :shape1 :strokes [{:stroke-color "#000000"
|
||||
:stroke-opacity 1
|
||||
:stroke-width 2}])))
|
||||
|
||||
;; ==== Action
|
||||
events
|
||||
[(dc/change-stroke #{(cthi/id :shape1)} {:color "#FABADA"} 0)]]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [;; ==== Get
|
||||
shape1' (get-in new-state [:workspace-data
|
||||
:pages-index
|
||||
(cthi/id :page1)
|
||||
:objects
|
||||
(cthi/id :shape1)])
|
||||
stroke' (-> (:strokes shape1')
|
||||
first)]
|
||||
|
||||
;; ==== Check
|
||||
(println stroke')
|
||||
(t/is (some? shape1'))
|
||||
(t/is (= (:stroke-alignment stroke') :center))))))))
|
57
frontend/test/frontend_tests/logic/frame_guides_test.cljs
Normal file
57
frontend/test/frontend_tests/logic/frame_guides_test.cljs
Normal file
|
@ -0,0 +1,57 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
(ns frontend-tests.logic.frame-guides-test
|
||||
(:require
|
||||
[app.common.test-helpers.compositions :as ctho]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.guides :as-alias dwg]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.pages :as thp]
|
||||
[frontend-tests.helpers.state :as ths]))
|
||||
|
||||
(t/use-fixtures :each
|
||||
{:before thp/reset-idmap!})
|
||||
|
||||
|
||||
(t/deftest test-remove-swap-slot-copy-paste-blue1-to-root
|
||||
(t/async
|
||||
done
|
||||
(let [;; ==== Setup
|
||||
file (-> (cthf/sample-file :file1)
|
||||
(ctho/add-frame :frame1))
|
||||
store (ths/setup-store file)
|
||||
frame1 (cths/get-shape file :frame1)
|
||||
|
||||
guide {:axis :x
|
||||
:frame-id (:id frame1)
|
||||
:id (uuid/next)
|
||||
:position 0}
|
||||
|
||||
;; ==== Action
|
||||
events
|
||||
[(dw/update-guides guide)
|
||||
(dw/update-position (:id frame1) {:x 100})]]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [;; ==== Get
|
||||
file' (ths/get-file-from-store new-state)
|
||||
page' (cthf/current-page file')
|
||||
|
||||
guide' (-> page'
|
||||
:options
|
||||
:guides
|
||||
(vals)
|
||||
(first))]
|
||||
;; ==== Check
|
||||
;; guide has moved
|
||||
(t/is (= (:position guide') 100))))))))
|
||||
|
||||
|
51
frontend/test/frontend_tests/logic/groups_test.cljs
Normal file
51
frontend/test/frontend_tests/logic/groups_test.cljs
Normal file
|
@ -0,0 +1,51 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
(ns frontend-tests.logic.groups-test
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.test-helpers.compositions :as ctho]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.groups :as dwgr]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.pages :as thp]
|
||||
[frontend-tests.helpers.state :as ths]))
|
||||
|
||||
(t/use-fixtures :each
|
||||
{:before thp/reset-idmap!})
|
||||
|
||||
|
||||
(t/deftest test-create-group
|
||||
(t/async
|
||||
done
|
||||
(let [;; ==== Setup
|
||||
file (-> (cthf/sample-file :file1)
|
||||
(cths/add-sample-shape :test-shape))
|
||||
store (ths/setup-store file)
|
||||
test-shape (cths/get-shape file :test-shape)
|
||||
|
||||
;; ==== Action
|
||||
events
|
||||
[(dws/select-shapes (d/ordered-set (:id test-shape)))
|
||||
(dwgr/group-selected)]]
|
||||
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [;; ==== Get
|
||||
file' (ths/get-file-from-store new-state)
|
||||
page' (cthf/current-page file')
|
||||
group-id (->> (:objects page')
|
||||
vals
|
||||
(filter #(= :group (:type %)))
|
||||
first
|
||||
:id)]
|
||||
;; ==== Check
|
||||
;; Group has been created and is selected
|
||||
(t/is (= (get-in new-state [:workspace-local :selected]) #{group-id}))))))))
|
|
@ -5109,6 +5109,10 @@ msgstr "Text (%s)"
|
|||
msgid "workspace.toolbar.text-palette"
|
||||
msgstr "Typographies (%s)"
|
||||
|
||||
#: src/app/main/ui/workspace/left_toolbar.cljs
|
||||
msgid "workspace.toolbar.toggle-toolbar"
|
||||
msgstr "Toggle toolbar"
|
||||
|
||||
msgid "workspace.top-bar.read-only.done"
|
||||
msgstr "Done"
|
||||
|
||||
|
|
|
@ -5271,6 +5271,10 @@ msgstr "Texto (%s)"
|
|||
msgid "workspace.toolbar.text-palette"
|
||||
msgstr "Tipografías (%s)"
|
||||
|
||||
#: src/app/main/ui/workspace/left_toolbar.cljs
|
||||
msgid "workspace.toolbar.toggle-toolbar"
|
||||
msgstr "Alternar barra de herramientas"
|
||||
|
||||
msgid "workspace.top-bar.read-only.done"
|
||||
msgstr "Hecho"
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue