mirror of
https://github.com/penpot/penpot.git
synced 2025-07-28 08:37:25 +02: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)
|
- 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)
|
- 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)
|
- 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
|
### :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 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)
|
- 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)
|
- 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 "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 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 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 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.0.3
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,19 @@
|
||||||
|
|
||||||
We want to thank to the amazing people that help us! Thank you! You're the best!
|
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
|
## Security
|
||||||
|
|
||||||
* Husnain Iqbal (CEO OF ALPHA INFERNO PVT LTD)
|
* Husnain Iqbal (CEO OF ALPHA INFERNO PVT LTD)
|
||||||
* [Shiraz Ali Khan](https://www.linkedin.com/in/shiraz-ali-khan-1ba508180/)
|
* [Shiraz Ali Khan](https://www.linkedin.com/in/shiraz-ali-khan-1ba508180/)
|
||||||
* Vaibhav Shukla
|
* Vaibhav Shukla
|
||||||
* Hassan Ahmed (Alias Xen Lee)
|
* Hassan Ahmed (Alias Xen Lee)
|
||||||
|
* Michal Biesiada (@mbiesiad)
|
||||||
|
|
||||||
## Internationalization
|
## Internationalization
|
||||||
|
|
||||||
* [00ff88](https://hosted.weblate.org/user/00ff88)
|
* [00ff88](https://hosted.weblate.org/user/00ff88)
|
||||||
* [AhmadHB](https://hosted.weblate.org/user/AhmadHB)
|
* [AhmadHB](https://hosted.weblate.org/user/AhmadHB)
|
||||||
* [Aimee](https://hosted.weblate.org/user/Aimee)
|
* [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)
|
* [zcraber](https://hosted.weblate.org/user/zcraber)
|
||||||
|
|
||||||
## Libraries & templates
|
## Libraries & templates
|
||||||
|
|
||||||
* systxema
|
* systxema
|
||||||
* plumilla
|
* plumilla
|
||||||
* victor crespo
|
* victor crespo
|
||||||
|
|
|
@ -87,7 +87,10 @@
|
||||||
:ldap-attrs-fullname "cn"
|
:ldap-attrs-fullname "cn"
|
||||||
|
|
||||||
;; a server prop key where initial project is stored.
|
;; 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 ::default-rpc-rlimit ::us/vector-of-strings)
|
||||||
(s/def ::rpc-rlimit-config ::fs/path)
|
(s/def ::rpc-rlimit-config ::fs/path)
|
||||||
|
@ -213,6 +216,7 @@
|
||||||
(s/def ::telemetry-uri ::us/string)
|
(s/def ::telemetry-uri ::us/string)
|
||||||
(s/def ::telemetry-with-taiga ::us/boolean)
|
(s/def ::telemetry-with-taiga ::us/boolean)
|
||||||
(s/def ::tenant ::us/string)
|
(s/def ::tenant ::us/string)
|
||||||
|
(s/def ::email-verify-threshold ::dt/duration)
|
||||||
|
|
||||||
(s/def ::config
|
(s/def ::config
|
||||||
(s/keys :opt-un [::secret-key
|
(s/keys :opt-un [::secret-key
|
||||||
|
@ -334,7 +338,8 @@
|
||||||
::telemetry-uri
|
::telemetry-uri
|
||||||
::telemetry-referer
|
::telemetry-referer
|
||||||
::telemetry-with-taiga
|
::telemetry-with-taiga
|
||||||
::tenant]))
|
::tenant
|
||||||
|
::email-verify-threshold]))
|
||||||
|
|
||||||
(def default-flags
|
(def default-flags
|
||||||
[:enable-backend-api-doc
|
[:enable-backend-api-doc
|
||||||
|
|
|
@ -38,13 +38,11 @@
|
||||||
(def schema:token
|
(def schema:token
|
||||||
[::sm/word-string {:max 6000}])
|
[::sm/word-string {:max 6000}])
|
||||||
|
|
||||||
(def ^:private default-verify-threshold
|
|
||||||
(dt/duration "15m"))
|
|
||||||
|
|
||||||
(defn- elapsed-verify-threshold?
|
(defn- elapsed-verify-threshold?
|
||||||
[profile]
|
[profile]
|
||||||
(let [elapsed (dt/diff (:modified-at profile) (dt/now))]
|
(let [elapsed (dt/diff (:modified-at profile) (dt/now))
|
||||||
(pos? (compare elapsed default-verify-threshold))))
|
verify-threshold (cf/get :email-verify-threshold)]
|
||||||
|
(pos? (compare elapsed verify-threshold))))
|
||||||
|
|
||||||
;; ---- COMMAND: login with password
|
;; ---- COMMAND: login with password
|
||||||
|
|
||||||
|
@ -130,12 +128,21 @@
|
||||||
|
|
||||||
;; ---- COMMAND: Logout
|
;; ---- COMMAND: Logout
|
||||||
|
|
||||||
|
(def ^:private schema:logout
|
||||||
|
[:map {:title "logoug"}
|
||||||
|
[:profile-id {:optional true} ::sm/uuid]])
|
||||||
|
|
||||||
(sv/defmethod ::logout
|
(sv/defmethod ::logout
|
||||||
"Clears the authentication cookie and logout the current session."
|
"Clears the authentication cookie and logout the current session."
|
||||||
{::rpc/auth false
|
{::rpc/auth false
|
||||||
::doc/added "1.15"}
|
::doc/changes [["2.1" "Now requires profile-id passed in the body"]]
|
||||||
[cfg _]
|
::doc/added "1.0"
|
||||||
(rph/with-transform {} (session/delete-fn cfg)))
|
::sm/params schema:logout}
|
||||||
|
[cfg params]
|
||||||
|
(if (= (:profile-id params)
|
||||||
|
(::rpc/profile-id params))
|
||||||
|
(rph/with-transform {} (session/delete-fn cfg))
|
||||||
|
{}))
|
||||||
|
|
||||||
;; ---- COMMAND: Recover Profile
|
;; ---- COMMAND: Recover Profile
|
||||||
|
|
||||||
|
|
|
@ -184,10 +184,7 @@
|
||||||
(ctk/instance-head? child))
|
(ctk/instance-head? child))
|
||||||
(let [slot (guess-swap-slot component-child component-container)]
|
(let [slot (guess-swap-slot component-child component-container)]
|
||||||
(l/dbg :hint "child" :id (:id child) :name (:name child) :slot slot)
|
(l/dbg :hint "child" :id (:id child) :name (:name child) :slot slot)
|
||||||
(ctn/update-shape container (:id child)
|
(ctn/update-shape container (:id child) #(ctk/set-swap-slot % slot)))
|
||||||
#(update % :touched
|
|
||||||
cfh/set-touched-group
|
|
||||||
(ctk/build-swap-slot-group slot))))
|
|
||||||
container)]
|
container)]
|
||||||
(recur (process-copy-head container child)
|
(recur (process-copy-head container child)
|
||||||
(rest children)
|
(rest children)
|
||||||
|
|
|
@ -481,7 +481,7 @@
|
||||||
(let [slot (:swap-slot args)]
|
(let [slot (:swap-slot args)]
|
||||||
(when (some? slot)
|
(when (some? slot)
|
||||||
(log/debug :hint (str " -> set swap-slot to " 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)
|
(log/dbg :hint "repairing shape :missing-slot" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||||
(-> (pcb/empty-changes nil page-id)
|
(-> (pcb/empty-changes nil page-id)
|
||||||
|
|
|
@ -284,9 +284,17 @@
|
||||||
(let [children (cfh/get-children-with-self (:objects container) shape-id)
|
(let [children (cfh/get-children-with-self (:objects container) shape-id)
|
||||||
skip-near (fn [changes shape]
|
skip-near (fn [changes shape]
|
||||||
(let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})]
|
(let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})]
|
||||||
(if (some? (:shape-ref ref-shape))
|
(cond-> changes
|
||||||
(pcb/update-shapes changes [(:id shape)] #(assoc % :shape-ref (:shape-ref ref-shape)))
|
(some? (:shape-ref ref-shape))
|
||||||
changes)))]
|
(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)))
|
(reduce skip-near changes children)))
|
||||||
|
|
||||||
(defn prepare-restore-component
|
(defn prepare-restore-component
|
||||||
|
@ -1194,7 +1202,7 @@
|
||||||
:shapes all-parents}))
|
:shapes all-parents}))
|
||||||
changes' (reduce del-obj-change changes' new-shapes)]
|
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
|
||||||
changes')))
|
changes')))
|
||||||
|
|
||||||
|
@ -1349,7 +1357,7 @@
|
||||||
changes'
|
changes'
|
||||||
ids)]
|
ids)]
|
||||||
|
|
||||||
(if (and (cfh/touched-group? parent :shapes-group) omit-touched?)
|
(if (and (ctk/touched-group? parent :shapes-group) omit-touched?)
|
||||||
changes
|
changes
|
||||||
changes')))
|
changes')))
|
||||||
|
|
||||||
|
@ -1385,7 +1393,7 @@
|
||||||
:ignore-touched true
|
:ignore-touched true
|
||||||
:syncing 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
|
||||||
changes')))
|
changes')))
|
||||||
|
|
||||||
|
@ -1846,12 +1854,11 @@
|
||||||
;; if the shape isn't inside a main component, it shouldn't have a swap slot
|
;; if the shape isn't inside a main component, it shouldn't have a swap slot
|
||||||
(and (nil? (ctk/get-swap-slot new-shape))
|
(and (nil? (ctk/get-swap-slot new-shape))
|
||||||
inside-comp?)
|
inside-comp?)
|
||||||
(update :touched cfh/set-touched-group (-> (ctf/find-swap-slot shape
|
(ctk/set-swap-slot (ctf/find-swap-slot shape
|
||||||
page
|
page
|
||||||
{:id (:id file)
|
{:id (:id file)
|
||||||
:data file}
|
:data file}
|
||||||
libraries)
|
libraries)))]
|
||||||
(ctk/build-swap-slot-group))))]
|
|
||||||
|
|
||||||
[new-shape (-> changes
|
[new-shape (-> changes
|
||||||
;; Restore the properties
|
;; Restore the properties
|
||||||
|
|
|
@ -183,6 +183,15 @@
|
||||||
(and (= shape-id (:main-instance-id component))
|
(and (= shape-id (:main-instance-id component))
|
||||||
(= page-id (:main-instance-page 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
|
(defn build-swap-slot-group
|
||||||
"Convert a swap-slot into a :touched group"
|
"Convert a swap-slot into a :touched group"
|
||||||
[swap-slot]
|
[swap-slot]
|
||||||
|
@ -204,6 +213,13 @@
|
||||||
(when group
|
(when group
|
||||||
(group->swap-slot 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?
|
(defn match-swap-slot?
|
||||||
[shape-main shape-inst]
|
[shape-main shape-inst]
|
||||||
(let [slot-main (get-swap-slot shape-main)
|
(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) {
|
static async init(page) {
|
||||||
await BaseWebSocketPage.initWebSockets(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, "get-teams", "logged-in-user/get-teams-default.json");
|
||||||
await BaseWebSocketPage.mockRPC(
|
await BaseWebSocketPage.mockRPC(
|
||||||
page,
|
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.presentUserListItems = page.getByTestId("active-users-list").getByAltText("Princesa Leia");
|
||||||
this.viewport = page.getByTestId("viewport");
|
this.viewport = page.getByTestId("viewport");
|
||||||
this.rootShape = page.locator(`[id="shape-00000000-0000-0000-0000-000000000000"]`);
|
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.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" });
|
||||||
|
this.toggleToolbarButton = page.getByRole("button", { name: "Toggle toolbar" });
|
||||||
this.colorpicker = page.getByTestId("colorpicker");
|
this.colorpicker = page.getByTestId("colorpicker");
|
||||||
this.layers = page.getByTestId("layers");
|
this.layers = page.getByTestId("layer-tree");
|
||||||
this.palette = page.getByTestId("palette");
|
this.palette = page.getByTestId("palette");
|
||||||
this.assets = page.getByTestId("assets");
|
this.sidebar = page.getByTestId("left-sidebar");
|
||||||
this.libraries = page.getByTestId("libraries");
|
this.selectionRect = page.getByTestId("workspace-selection-rect");
|
||||||
this.closeLibraries = page.getByTestId("close-libraries");
|
this.horizontalScrollbar = page.getByTestId("horizontal-scrollbar");
|
||||||
this.librariesModal = page.getByTestId("libraries-modal");
|
this.librariesModal = page.getByTestId("libraries-modal");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +104,19 @@ export class WorkspacePage extends BaseWebSocketPage {
|
||||||
await this.page.mouse.up();
|
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) {
|
async moveSelectionToShape(name) {
|
||||||
await this.page.locator('rect.viewport-selrect').hover();
|
await this.page.locator('rect.viewport-selrect').hover();
|
||||||
await this.page.mouse.down();
|
await this.page.mouse.down();
|
||||||
|
@ -120,15 +135,21 @@ export class WorkspacePage extends BaseWebSocketPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
async expectSelectedLayer(name) {
|
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 = {}) {
|
async clickAssets(clickOptions = {}) {
|
||||||
await this.assets.click(clickOptions);
|
await this.sidebar.getByText("Assets").click(clickOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clickLibraries(clickOptions = {}) {
|
async openLibrariesModal(clickOptions = {}) {
|
||||||
await this.libraries.click(clickOptions);
|
await this.sidebar.getByText("Libraries").click(clickOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clickLibrary(name, clickOptions = {}) {
|
async clickLibrary(name, clickOptions = {}) {
|
||||||
|
@ -136,11 +157,15 @@ export class WorkspacePage extends BaseWebSocketPage {
|
||||||
.getByTestId("library-item")
|
.getByTestId("library-item")
|
||||||
.filter({ hasText: name })
|
.filter({ hasText: name })
|
||||||
.getByRole("button")
|
.getByRole("button")
|
||||||
.click(clickOptions);
|
.click(clickOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async clickCloseLibraries(clickOptions = {}) {
|
async closeLibrariesModal(clickOptions = {}) {
|
||||||
await this.closeLibraries.click(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 = {}) {
|
async clickColorPalette(clickOptions = {}) {
|
||||||
|
|
|
@ -3,6 +3,11 @@ import DashboardPage from "../pages/DashboardPage";
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await DashboardPage.init(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 }) => {
|
test("Dashboad page has title ", async ({ page }) => {
|
||||||
|
|
|
@ -7,6 +7,8 @@ test.beforeEach(async ({ page }) => {
|
||||||
|
|
||||||
const multipleConstraintsFileId = `03bff843-920f-81a1-8004-756365e1eb6a`;
|
const multipleConstraintsFileId = `03bff843-920f-81a1-8004-756365e1eb6a`;
|
||||||
const multipleConstraintsPageId = `03bff843-920f-81a1-8004-756365e1eb6b`;
|
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) => {
|
const setupFileWithMultipeConstraints = async (workspace) => {
|
||||||
await workspace.setupEmptyFile();
|
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.describe("Constraints", () => {
|
||||||
test("Constraint dropdown shows 'Mixed' when multiple layers are selected with different constraints", async ({
|
test("Constraint dropdown shows 'Mixed' when multiple layers are selected with different constraints", async ({
|
||||||
page,
|
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 }) => {
|
test("BUG 7760 - Layout losing properties when changing parents", async ({ page }) => {
|
||||||
const workspacePage = new WorkspacePage(page);
|
const workspacePage = new WorkspacePage(page);
|
||||||
await workspacePage.setupEmptyFile();
|
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");
|
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 }) => {
|
test("User adds a library and its automatically selected in the color palette", async ({ page }) => {
|
||||||
const workspacePage = new WorkspacePage(page);
|
const workspacePage = new WorkspacePage(page);
|
||||||
await workspacePage.setupEmptyFile();
|
await workspacePage.setupEmptyFile();
|
||||||
|
@ -53,31 +107,16 @@ test("User adds a library and its automatically selected in the color palette",
|
||||||
await workspacePage.clickAssets();
|
await workspacePage.clickAssets();
|
||||||
// Now the get-file call should return a library
|
// Now the get-file call should return a library
|
||||||
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
|
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
|
||||||
await workspacePage.clickLibraries();
|
await workspacePage.openLibrariesModal();
|
||||||
await workspacePage.clickLibrary("Testing library 1")
|
await workspacePage.clickLibrary("Testing library 1")
|
||||||
await workspacePage.clickCloseLibraries();
|
await workspacePage.closeLibrariesModal();
|
||||||
|
|
||||||
await expect(workspacePage.palette.getByRole("button", { name: "test-color-187cd5" })).toBeVisible();
|
await expect(workspacePage.palette.getByRole("button", { name: "test-color-187cd5" })).toBeVisible();
|
||||||
|
|
||||||
// Remove Testing library 1
|
// Remove Testing library 1
|
||||||
await workspacePage.clickLibraries();
|
await workspacePage.openLibrariesModal();
|
||||||
await workspacePage.clickLibrary("Testing library 1")
|
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();
|
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 [_] {})
|
(-data [_] {})
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ state _]
|
||||||
(->> (rp/cmd! :logout)
|
(let [profile-id (:profile-id state)]
|
||||||
(rx/delay-at-least 300)
|
(->> (rx/interval 500)
|
||||||
(rx/catch (constantly (rx/of 1)))
|
(rx/take 1)
|
||||||
(rx/map #(logged-out params)))))))
|
(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
|
;; --- Update Profile
|
||||||
|
|
||||||
|
|
|
@ -248,7 +248,7 @@
|
||||||
(assoc :stroke-style :solid)
|
(assoc :stroke-style :solid)
|
||||||
|
|
||||||
(not (contains? new-attrs :stroke-alignment))
|
(not (contains? new-attrs :stroke-alignment))
|
||||||
(assoc :stroke-alignment :inner)
|
(assoc :stroke-alignment :center)
|
||||||
|
|
||||||
:always
|
:always
|
||||||
(d/without-nils))]
|
(d/without-nils))]
|
||||||
|
|
|
@ -198,7 +198,8 @@
|
||||||
(dws/select-shapes (d/ordered-set (:id group))))
|
(dws/select-shapes (d/ordered-set (:id group))))
|
||||||
(ptk/data-event :layout/update {:ids parents}))))))))
|
(ptk/data-event :layout/update {:ids parents}))))))))
|
||||||
|
|
||||||
(def group-selected
|
(defn group-selected
|
||||||
|
[]
|
||||||
(ptk/reify ::group-selected
|
(ptk/reify ::group-selected
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
|
@ -258,7 +259,8 @@
|
||||||
(when change-selection?
|
(when change-selection?
|
||||||
(dws/select-shapes child-ids))))))))
|
(dws/select-shapes child-ids))))))))
|
||||||
|
|
||||||
(def ungroup-selected
|
(defn ungroup-selected
|
||||||
|
[]
|
||||||
(ptk/reify ::ungroup-selected
|
(ptk/reify ::ungroup-selected
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
|
|
|
@ -79,20 +79,21 @@
|
||||||
(rx/from (->> guides (mapv #(remove-guide %))))))))
|
(rx/from (->> guides (mapv #(remove-guide %))))))))
|
||||||
|
|
||||||
(defmethod ptk/resolve ::move-frame-guides
|
(defmethod ptk/resolve ::move-frame-guides
|
||||||
[_ ids]
|
[_ args]
|
||||||
(dm/assert!
|
(dm/assert!
|
||||||
"expected a coll of uuids"
|
"expected a coll of uuids"
|
||||||
(every? uuid? ids))
|
(every? uuid? (:ids args)))
|
||||||
(ptk/reify ::move-frame-guides
|
(ptk/reify ::move-frame-guides
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(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])))
|
is-frame? (fn [id] (= :frame (get-in objects [id :type])))
|
||||||
frame-ids? (into #{} (filter is-frame?) ids)
|
frame-ids? (into #{} (filter is-frame?) ids)
|
||||||
|
|
||||||
object-modifiers (get state :workspace-modifiers)
|
|
||||||
|
|
||||||
build-move-event
|
build-move-event
|
||||||
(fn [guide]
|
(fn [guide]
|
||||||
(let [frame (get objects (:frame-id guide))
|
(let [frame (get objects (:frame-id guide))
|
||||||
|
|
|
@ -497,7 +497,7 @@
|
||||||
(if undo-transation?
|
(if undo-transation?
|
||||||
(rx/of (dwu/start-undo-transaction undo-id))
|
(rx/of (dwu/start-undo-transaction undo-id))
|
||||||
(rx/empty))
|
(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)
|
(ptk/event ::dwcm/move-frame-comment-threads ids-with-children)
|
||||||
(dwsh/update-shapes
|
(dwsh/update-shapes
|
||||||
ids
|
ids
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
(or (= type ::common/finish-path)
|
(or (= type ::common/finish-path)
|
||||||
(= type :app.main.data.workspace.path.shortcuts/esc-pressed)
|
(= type :app.main.data.workspace.path.shortcuts/esc-pressed)
|
||||||
(= type :app.main.data.workspace.common/clear-edition-mode)
|
(= 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)
|
(= type :app.main.data.workspace/finalize-page)
|
||||||
(= event :interrupt) ;; ESC
|
(= event :interrupt) ;; ESC
|
||||||
(and ^boolean (mse/mouse-event? event)
|
(and ^boolean (mse/mouse-event? event)
|
||||||
|
|
|
@ -119,12 +119,12 @@
|
||||||
:group {:tooltip (ds/meta "G")
|
:group {:tooltip (ds/meta "G")
|
||||||
:command (ds/c-mod "g")
|
:command (ds/c-mod "g")
|
||||||
:subsections [:modify-layers]
|
:subsections [:modify-layers]
|
||||||
:fn #(emit-when-no-readonly dw/group-selected)}
|
:fn #(emit-when-no-readonly (dw/group-selected))}
|
||||||
|
|
||||||
:ungroup {:tooltip (ds/shift "G")
|
:ungroup {:tooltip (ds/shift "G")
|
||||||
:command "shift+g"
|
:command "shift+g"
|
||||||
:subsections [:modify-layers]
|
:subsections [:modify-layers]
|
||||||
:fn #(emit-when-no-readonly dw/ungroup-selected)}
|
:fn #(emit-when-no-readonly (dw/ungroup-selected))}
|
||||||
|
|
||||||
:mask {:tooltip (ds/meta "M")
|
:mask {:tooltip (ds/meta "M")
|
||||||
:command (ds/c-mod "m")
|
:command (ds/c-mod "m")
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.util.i18n :refer [tr tr-html]]
|
[app.util.i18n :refer [tr tr-html]]
|
||||||
[app.util.router :as rt]
|
[app.util.router :as rt]
|
||||||
|
[app.util.storage :as sto]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
@ -163,11 +164,7 @@
|
||||||
|
|
||||||
;; --- PAGE: register validation
|
;; --- PAGE: register validation
|
||||||
|
|
||||||
(defn- handle-register-error
|
(defn- on-register-success
|
||||||
[_form _data]
|
|
||||||
(st/emit! (msg/error (tr "errors.generic"))))
|
|
||||||
|
|
||||||
(defn- handle-register-success
|
|
||||||
[data]
|
[data]
|
||||||
(cond
|
(cond
|
||||||
(some? (:invitation-token data))
|
(some? (:invitation-token data))
|
||||||
|
@ -178,7 +175,9 @@
|
||||||
(st/emit! (du/login-from-register))
|
(st/emit! (du/login-from-register))
|
||||||
|
|
||||||
:else
|
: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-terms-and-privacy (s/and ::us/boolean true?))
|
||||||
(s/def ::accept-newsletter-subscription ::us/boolean)
|
(s/def ::accept-newsletter-subscription ::us/boolean)
|
||||||
|
@ -192,31 +191,63 @@
|
||||||
:opt-un [::accept-terms-and-privacy
|
:opt-un [::accept-terms-and-privacy
|
||||||
::accept-newsletter-subscription])))
|
::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/defc register-validate-form
|
||||||
|
{::mf/props :obj}
|
||||||
[{:keys [params on-success-callback]}]
|
[{:keys [params on-success-callback]}]
|
||||||
(let [form (fm/use-form :spec ::register-validate-form
|
(let [validators (mf/with-memo []
|
||||||
:validators [(fm/validate-not-empty :fullname (tr "auth.name.not-all-space"))
|
[(fm/validate-not-empty :fullname (tr "auth.name.not-all-space"))
|
||||||
(fm/validate-length :fullname fm/max-length-allowed (tr "auth.name.too-long"))]
|
(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)
|
:initial params)
|
||||||
|
|
||||||
submitted? (mf/use-state false)
|
submitted? (mf/use-state false)
|
||||||
|
|
||||||
on-success (fn [p]
|
on-success
|
||||||
(if (nil? on-success-callback)
|
(mf/use-fn
|
||||||
(handle-register-success p)
|
(mf/deps on-success-callback)
|
||||||
(on-success-callback (:email p))))
|
(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
|
on-submit
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [form _event]
|
(fn [form _]
|
||||||
(reset! submitted? true)
|
(reset! submitted? true)
|
||||||
(let [params (:clean-data @form)]
|
(let [params (:clean-data @form)]
|
||||||
(->> (rp/cmd! :register-profile params)
|
(->> (rp/cmd! :register-profile params)
|
||||||
(rx/finalize #(reset! submitted? false))
|
(rx/finalize #(reset! submitted? false))
|
||||||
(rx/subs! on-success
|
(rx/subs! on-success on-error)))))]
|
||||||
(partial handle-register-error form))))))]
|
|
||||||
|
|
||||||
[:& fm/form {:on-submit on-submit :form form
|
[:& fm/form {:on-submit on-submit
|
||||||
|
:form form
|
||||||
:class (stl/css :register-validate-form)}
|
:class (stl/css :register-validate-form)}
|
||||||
|
|
||||||
[:div {:class (stl/css :fields-row)}
|
[:div {:class (stl/css :fields-row)}
|
||||||
[:& fm/input {:name :fullname
|
[:& fm/input {:name :fullname
|
||||||
:label (tr "auth.fullname")
|
:label (tr "auth.fullname")
|
||||||
|
@ -225,18 +256,7 @@
|
||||||
:class (stl/css :form-field)}]]
|
:class (stl/css :form-field)}]]
|
||||||
|
|
||||||
(when (contains? cf/flags :terms-and-privacy-checkbox)
|
(when (contains? cf/flags :terms-and-privacy-checkbox)
|
||||||
(let [terms-label
|
[:& terms-and-privacy])
|
||||||
(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}]]))
|
|
||||||
|
|
||||||
[:> fm/submit-button*
|
[:> fm/submit-button*
|
||||||
{:label (tr "auth.register-submit")
|
{:label (tr "auth.register-submit")
|
||||||
|
@ -245,6 +265,7 @@
|
||||||
|
|
||||||
|
|
||||||
(mf/defc register-validate-page
|
(mf/defc register-validate-page
|
||||||
|
{::mf/props :obj}
|
||||||
[{:keys [params]}]
|
[{:keys [params]}]
|
||||||
[:div {:class (stl/css :auth-form-wrapper)}
|
[:div {:class (stl/css :auth-form-wrapper)}
|
||||||
[:h1 {:class (stl/css :logo-container)}
|
[:h1 {:class (stl/css :logo-container)}
|
||||||
|
@ -263,13 +284,15 @@
|
||||||
(tr "labels.go-back")]]]])
|
(tr "labels.go-back")]]]])
|
||||||
|
|
||||||
(mf/defc register-success-page
|
(mf/defc register-success-page
|
||||||
[{:keys [params]}]
|
{::mf/props :obj}
|
||||||
[:div {:class (stl/css :auth-form-wrapper :register-success)}
|
[]
|
||||||
[:h1 {:class (stl/css :logo-container)}
|
(let [email (::email @sto/storage)]
|
||||||
[:a {:href "#/" :title "Penpot" :class (stl/css :logo-btn)} i/logo]]
|
[:div {:class (stl/css :auth-form-wrapper :register-success)}
|
||||||
[:div {:class (stl/css :auth-title-wrapper)}
|
[:h1 {:class (stl/css :logo-container)}
|
||||||
[:h2 {:class (stl/css :auth-title)}
|
[:a {:href "#/" :title "Penpot" :class (stl/css :logo-btn)} i/logo]]
|
||||||
(tr "auth.check-mail")]
|
[:div {:class (stl/css :auth-title-wrapper)}
|
||||||
[:div {:class (stl/css :notification-text)} (tr "auth.verification-email-sent")]]
|
[:h2 {:class (stl/css :auth-title)}
|
||||||
[:div {:class (stl/css :notification-text-email)} (:email params "")]
|
(tr "auth.check-mail")]
|
||||||
[:div {:class (stl/css :notification-text)} (tr "auth.check-your-email")]])
|
[: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
|
||||||
.thread-content {
|
.thread-content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
overflow-y: scroll;
|
overflow-y: auto;
|
||||||
scrollbar-gutter: stable;
|
|
||||||
width: $s-284;
|
width: $s-284;
|
||||||
padding: $s-12;
|
padding: $s-12;
|
||||||
padding-inline-end: 0;
|
padding-inline-end: $s-8;
|
||||||
|
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
|
|
|
@ -44,9 +44,17 @@
|
||||||
(or (empty? overlays-ids) (nil? shape) (cfh/root? shape)) base-frame
|
(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)))
|
: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
|
(defn- activate-interaction
|
||||||
[interaction shape base-frame frame-offset objects overlays]
|
[interaction shape base-frame frame-offset objects overlays]
|
||||||
|
|
||||||
(case (:action-type interaction)
|
(case (:action-type interaction)
|
||||||
:navigate
|
:navigate
|
||||||
(when-let [frame-id (:destination interaction)]
|
(when-let [frame-id (:destination interaction)]
|
||||||
|
@ -58,9 +66,11 @@
|
||||||
(dv/go-to-frame frame-id (:animation interaction)))))
|
(dv/go-to-frame frame-id (:animation interaction)))))
|
||||||
|
|
||||||
:open-overlay
|
: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)
|
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"
|
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
||||||
(:frame-id shape)
|
(:frame-id shape)
|
||||||
(:id shape))
|
(:id shape))
|
||||||
|
@ -88,7 +98,9 @@
|
||||||
fixed-base?))))
|
fixed-base?))))
|
||||||
|
|
||||||
:toggle-overlay
|
: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)
|
dest-frame (get objects dest-frame-id)
|
||||||
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
||||||
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
||||||
|
@ -146,7 +158,9 @@
|
||||||
(st/emit! (dv/close-overlay frame-id)))
|
(st/emit! (dv/close-overlay frame-id)))
|
||||||
|
|
||||||
:toggle-overlay
|
: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)
|
dest-frame (get objects dest-frame-id)
|
||||||
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
||||||
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
||||||
|
@ -178,7 +192,9 @@
|
||||||
|
|
||||||
|
|
||||||
:close-overlay
|
: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)
|
dest-frame (get objects dest-frame-id)
|
||||||
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
relative-to-id (if (= :manual (:overlay-pos-type interaction))
|
||||||
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
(if (= (:type shape) :frame) ;; manual interactions are always from "self"
|
||||||
|
|
|
@ -243,8 +243,8 @@
|
||||||
is-group? (and single? has-group?)
|
is-group? (and single? has-group?)
|
||||||
is-bool? (and single? has-bool?)
|
is-bool? (and single? has-bool?)
|
||||||
|
|
||||||
do-create-group #(st/emit! dw/group-selected)
|
do-create-group #(st/emit! (dw/group-selected))
|
||||||
do-remove-group #(st/emit! dw/ungroup-selected)
|
do-remove-group #(st/emit! (dw/ungroup-selected))
|
||||||
do-mask-group #(st/emit! (dw/mask-group))
|
do-mask-group #(st/emit! (dw/mask-group))
|
||||||
do-unmask-group #(st/emit! (dw/unmask-group))
|
do-unmask-group #(st/emit! (dw/unmask-group))
|
||||||
do-create-artboard-from-selection
|
do-create-artboard-from-selection
|
||||||
|
|
|
@ -519,6 +519,7 @@
|
||||||
[:div {:class (stl/css :modal-dialog)}
|
[:div {:class (stl/css :modal-dialog)}
|
||||||
[:button {:class (stl/css :close-btn)
|
[:button {:class (stl/css :close-btn)
|
||||||
:on-click close-dialog
|
:on-click close-dialog
|
||||||
|
:aria-label (tr "labels.close")
|
||||||
:data-testid "close-libraries"}
|
:data-testid "close-libraries"}
|
||||||
close-icon]
|
close-icon]
|
||||||
[:div {:class (stl/css :modal-title)}
|
[:div {:class (stl/css :modal-title)}
|
||||||
|
|
|
@ -38,8 +38,7 @@
|
||||||
(let [options-mode (mf/deref refs/options-mode-global)
|
(let [options-mode (mf/deref refs/options-mode-global)
|
||||||
mode-inspect? (= options-mode :inspect)
|
mode-inspect? (= options-mode :inspect)
|
||||||
project (mf/deref refs/workspace-project)
|
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
|
section (cond (or mode-inspect? (contains? layout :layers)) :layers
|
||||||
(contains? layout :assets) :assets)
|
(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}
|
{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)
|
(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)
|
(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
|
handle-collapse
|
||||||
(mf/use-fn #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
(mf/use-fn #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)))
|
||||||
|
@ -63,6 +65,7 @@
|
||||||
[:& (mf/provider muc/sidebar) {:value :left}
|
[:& (mf/provider muc/sidebar) {:value :left}
|
||||||
[:aside {:ref parent-ref
|
[:aside {:ref parent-ref
|
||||||
:id "left-sidebar-aside"
|
:id "left-sidebar-aside"
|
||||||
|
:data-testid "left-sidebar"
|
||||||
:data-size (str size)
|
:data-size (str size)
|
||||||
:class (stl/css-case :left-settings-bar true
|
:class (stl/css-case :left-settings-bar true
|
||||||
:global/two-row (<= size 300)
|
:global/two-row (<= size 300)
|
||||||
|
|
|
@ -84,10 +84,8 @@ $width-settings-bar-max: $s-500;
|
||||||
|
|
||||||
.resize-area-horiz {
|
.resize-area-horiz {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
// top: calc($s-88 + var(--height, 200px));
|
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
// height: $s-8;
|
|
||||||
border-bottom: $s-2 solid var(--resize-area-border-color);
|
border-bottom: $s-2 solid var(--resize-area-border-color);
|
||||||
cursor: ns-resize;
|
cursor: ns-resize;
|
||||||
}
|
}
|
||||||
|
|
|
@ -510,7 +510,7 @@
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
#(st/emit! (dw/toggle-focus-mode)))]
|
#(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)
|
(if (d/not-empty? focus)
|
||||||
[:div {:class (stl/css :tool-window-bar)}
|
[:div {:class (stl/css :tool-window-bar)}
|
||||||
[:button {:class (stl/css :focus-title)
|
[:button {:class (stl/css :focus-title)
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
:class (stl/css-case :title-spacing-blur (not has-value?))}
|
:class (stl/css-case :title-spacing-blur (not has-value?))}
|
||||||
(when-not has-value?
|
(when-not has-value?
|
||||||
[:button {:class (stl/css :add-blur)
|
[:button {:class (stl/css :add-blur)
|
||||||
|
:data-testid "add-blur"
|
||||||
:on-click handle-add} i/add])]]
|
:on-click handle-add} i/add])]]
|
||||||
(when (and open? has-value?)
|
(when (and open? has-value?)
|
||||||
[:div {:class (stl/css :element-set-content)}
|
[:div {:class (stl/css :element-set-content)}
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
(mf/deps adjust-textarea-size creating?)
|
(mf/deps adjust-textarea-size creating?)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
|
(rerender-fn)
|
||||||
(when-let [textarea (mf/ref-val textarea-ref)]
|
(when-let [textarea (mf/ref-val textarea-ref)]
|
||||||
(dom/set-value! textarea annotation)
|
(dom/set-value! textarea annotation)
|
||||||
(reset! editing* false)
|
(reset! editing* false)
|
||||||
|
@ -98,6 +99,7 @@
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
|
(rerender-fn)
|
||||||
(when ^boolean main-instance?
|
(when ^boolean main-instance?
|
||||||
(when-let [textarea (mf/ref-val textarea-ref)]
|
(when-let [textarea (mf/ref-val textarea-ref)]
|
||||||
(reset! editing* true)
|
(reset! editing* true)
|
||||||
|
@ -109,6 +111,7 @@
|
||||||
(mf/deps creating?)
|
(mf/deps creating?)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
|
(rerender-fn)
|
||||||
(when-let [textarea (mf/ref-val textarea-ref)]
|
(when-let [textarea (mf/ref-val textarea-ref)]
|
||||||
(let [text (dom/get-value textarea)]
|
(let [text (dom/get-value textarea)]
|
||||||
(when-not (str/blank? text)
|
(when-not (str/blank? text)
|
||||||
|
@ -124,6 +127,7 @@
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(let [on-accept (fn []
|
(let [on-accept (fn []
|
||||||
|
(rerender-fn)
|
||||||
(st/emit!
|
(st/emit!
|
||||||
;; (ptk/data-event {::ev/name "delete-component-annotation"})
|
;; (ptk/data-event {::ev/name "delete-component-annotation"})
|
||||||
(when creating?
|
(when creating?
|
||||||
|
|
|
@ -146,6 +146,7 @@
|
||||||
|
|
||||||
(when (and (not disable-remove?) (not (= :multiple fills)))
|
(when (and (not disable-remove?) (not (= :multiple fills)))
|
||||||
[:button {:class (stl/css :add-fill)
|
[:button {:class (stl/css :add-fill)
|
||||||
|
:data-testid "add-fill"
|
||||||
:on-click on-add} i/add])]]
|
:on-click on-add} i/add])]]
|
||||||
|
|
||||||
(when open?
|
(when open?
|
||||||
|
|
|
@ -298,6 +298,7 @@
|
||||||
|
|
||||||
(when-not (= :multiple shadows)
|
(when-not (= :multiple shadows)
|
||||||
[:button {:class (stl/css :add-shadow)
|
[:button {:class (stl/css :add-shadow)
|
||||||
|
:data-testid "add-shadow"
|
||||||
:on-click on-add-shadow} i/add])]]
|
:on-click on-add-shadow} i/add])]]
|
||||||
|
|
||||||
(when open?
|
(when open?
|
||||||
|
|
|
@ -169,9 +169,10 @@
|
||||||
:on-collapsed toggle-content
|
:on-collapsed toggle-content
|
||||||
:title label
|
:title label
|
||||||
:class (stl/css-case :title-spacing-stroke (not has-strokes?))}
|
:class (stl/css-case :title-spacing-stroke (not has-strokes?))}
|
||||||
|
(when (not (= :multiple strokes))
|
||||||
[:button {:class (stl/css :add-stroke)
|
[:button {:class (stl/css :add-stroke)
|
||||||
:on-click on-add-stroke} i/add]]]
|
:data-testid "add-stroke"
|
||||||
|
:on-click on-add-stroke} i/add])]]
|
||||||
(when open?
|
(when open?
|
||||||
[:div {:class (stl/css-case :element-content true
|
[:div {:class (stl/css-case :element-content true
|
||||||
:empty-content (not has-strokes?))}
|
:empty-content (not has-strokes?))}
|
||||||
|
|
|
@ -205,7 +205,6 @@
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(st/emit! (dw/create-page {:file-id file-id :project-id project-id}))
|
(st/emit! (dw/create-page {:file-id file-id :project-id project-id}))
|
||||||
(-> event dom/get-current-target dom/blur!)))
|
(-> event dom/get-current-target dom/blur!)))
|
||||||
size (if show-pages? size 32)
|
|
||||||
read-only? (mf/use-ctx ctx/workspace-read-only?)]
|
read-only? (mf/use-ctx ctx/workspace-read-only?)]
|
||||||
|
|
||||||
[:div {:class (stl/css :sitemap)
|
[:div {:class (stl/css :sitemap)
|
||||||
|
|
|
@ -115,13 +115,16 @@
|
||||||
|
|
||||||
toggle-toolbar
|
toggle-toolbar
|
||||||
(mf/use-fn
|
(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?
|
(when-not ^boolean read-only?
|
||||||
[:aside {:class (stl/css-case :main-toolbar true
|
[:aside {:class (stl/css-case :main-toolbar true
|
||||||
:main-toolbar-no-rulers (not rulers?)
|
:main-toolbar-no-rulers (not rulers?)
|
||||||
:main-toolbar-hidden hide-toolbar?)}
|
:main-toolbar-hidden hide-toolbar?)}
|
||||||
[:ul {:class (stl/css :main-toolbar-options)}
|
[:ul {:class (stl/css :main-toolbar-options)
|
||||||
|
:data-testid "toolbar-options"}
|
||||||
[:li
|
[:li
|
||||||
[:button
|
[:button
|
||||||
{:title (tr "workspace.toolbar.move" (sc/get-tooltip :move))
|
{:title (tr "workspace.toolbar.move" (sc/get-tooltip :move))
|
||||||
|
@ -197,7 +200,9 @@
|
||||||
:on-click toggle-debug-panel}
|
:on-click toggle-debug-panel}
|
||||||
i/bug]])]]
|
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}
|
:on-click toggle-toolbar}
|
||||||
[:div {:class (stl/css :toolbar-handler-btn)}]]])))
|
[:div {:class (stl/css :toolbar-handler-btn)}]]])))
|
||||||
|
|
||||||
|
|
|
@ -636,8 +636,8 @@
|
||||||
:objects base-objects
|
:objects base-objects
|
||||||
:modifiers modifiers
|
:modifiers modifiers
|
||||||
:shape frame
|
:shape frame
|
||||||
:view-only true}]))
|
:view-only true}]))]
|
||||||
|
[:g.scrollbar-wrapper {:clipPath "url(#clip-handlers)"}
|
||||||
[:& scroll-bars/viewport-scrollbars
|
[:& scroll-bars/viewport-scrollbars
|
||||||
{:objects base-objects
|
{:objects base-objects
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
|
|
|
@ -196,7 +196,8 @@
|
||||||
|
|
||||||
[:*
|
[:*
|
||||||
(when show-v-scroll?
|
(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)
|
[:rect {:on-pointer-move #(on-pointer-move % :y)
|
||||||
:on-pointer-down #(on-pointer-down % :y)
|
:on-pointer-down #(on-pointer-down % :y)
|
||||||
:on-pointer-up on-pointer-up
|
:on-pointer-up on-pointer-up
|
||||||
|
@ -210,7 +211,8 @@
|
||||||
:style {:stroke "white"
|
:style {:stroke "white"
|
||||||
:stroke-width (/ 0.15 zoom)}}]])
|
:stroke-width (/ 0.15 zoom)}}]])
|
||||||
(when show-h-scroll?
|
(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)
|
[:rect {:on-pointer-move #(on-pointer-move % :x)
|
||||||
:on-pointer-down #(on-pointer-down % :x)
|
:on-pointer-down #(on-pointer-down % :x)
|
||||||
:on-pointer-up on-pointer-up
|
:on-pointer-up on-pointer-up
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
[:rect.selection-rect
|
[:rect.selection-rect
|
||||||
{:x (:x data)
|
{:x (:x data)
|
||||||
:y (:y data)
|
:y (:y data)
|
||||||
|
:data-testid "workspace-selection-rect"
|
||||||
:width (:width data)
|
:width (:width data)
|
||||||
:height (:height data)
|
:height (:height data)
|
||||||
:style {;; Primary with 0.1 opacity
|
:style {;; Primary with 0.1 opacity
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
[app.common.test-helpers.files :as cthf]
|
[app.common.test-helpers.files :as cthf]
|
||||||
[app.common.test-helpers.ids-map :as cthi]
|
[app.common.test-helpers.ids-map :as cthi]
|
||||||
[app.common.test-helpers.shapes :as cths]
|
[app.common.test-helpers.shapes :as cths]
|
||||||
|
[app.main.data.workspace.colors :as dc]
|
||||||
[app.main.data.workspace.shapes :as dwsh]
|
[app.main.data.workspace.shapes :as dwsh]
|
||||||
[cljs.test :as t :include-macros true]
|
[cljs.test :as t :include-macros true]
|
||||||
[frontend-tests.helpers.state :as ths]))
|
[frontend-tests.helpers.state :as ths]))
|
||||||
|
@ -46,3 +47,36 @@
|
||||||
(t/is (= (count fills') 1))
|
(t/is (= (count fills') 1))
|
||||||
(t/is (= (:fill-color fill') "#fabada"))
|
(t/is (= (:fill-color fill') "#fabada"))
|
||||||
(t/is (= (:fill-opacity fill') 1))))))))
|
(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"
|
msgid "workspace.toolbar.text-palette"
|
||||||
msgstr "Typographies (%s)"
|
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"
|
msgid "workspace.top-bar.read-only.done"
|
||||||
msgstr "Done"
|
msgstr "Done"
|
||||||
|
|
||||||
|
|
|
@ -5271,6 +5271,10 @@ msgstr "Texto (%s)"
|
||||||
msgid "workspace.toolbar.text-palette"
|
msgid "workspace.toolbar.text-palette"
|
||||||
msgstr "Tipografías (%s)"
|
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"
|
msgid "workspace.top-bar.read-only.done"
|
||||||
msgstr "Hecho"
|
msgstr "Hecho"
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue