mirror of
https://github.com/penpot/penpot.git
synced 2025-03-11 23:31:21 -05:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
47b455ba87
38 changed files with 1238 additions and 215 deletions
|
@ -324,19 +324,21 @@
|
|||
(update :data cpc/process-changes changes)
|
||||
(update :data d/without-nils))]
|
||||
|
||||
(when (contains? cf/flags :soft-file-validation)
|
||||
(soft-validate-file! file libs))
|
||||
|
||||
(when (contains? cf/flags :soft-file-schema-validation)
|
||||
(soft-validate-file-schema! file))
|
||||
(binding [pmap/*tracked* nil]
|
||||
(when (contains? cf/flags :soft-file-validation)
|
||||
(soft-validate-file! file libs))
|
||||
|
||||
(when (and (contains? cf/flags :file-validation)
|
||||
(not skip-validate))
|
||||
(val/validate-file! file libs))
|
||||
(when (contains? cf/flags :soft-file-schema-validation)
|
||||
(soft-validate-file-schema! file))
|
||||
|
||||
(when (and (contains? cf/flags :file-schema-validation)
|
||||
(not skip-validate))
|
||||
(val/validate-file-schema! file))
|
||||
(when (and (contains? cf/flags :file-validation)
|
||||
(not skip-validate))
|
||||
(val/validate-file! file libs))
|
||||
|
||||
(when (and (contains? cf/flags :file-schema-validation)
|
||||
(not skip-validate))
|
||||
(val/validate-file-schema! file)))
|
||||
|
||||
(cond-> file
|
||||
(contains? cfeat/*current* "fdata/objects-map")
|
||||
|
|
|
@ -473,6 +473,59 @@
|
|||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :duplicate-slot
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
childs (map #(get (:objects page) %) (:shapes shape))
|
||||
child-with-duplicate (let [result (reduce (fn [[seen duplicates] item]
|
||||
(let [swap-slot (ctk/get-swap-slot item)]
|
||||
(if (contains? seen swap-slot)
|
||||
[seen (conj duplicates item)]
|
||||
[(conj seen swap-slot) duplicates])))
|
||||
[#{} []]
|
||||
childs)]
|
||||
(second result))
|
||||
repair-shape
|
||||
(fn [shape]
|
||||
;; Remove the swap slot
|
||||
(log/debug :hint " -> remove swap-slot" :child-id (:id shape))
|
||||
(ctk/remove-swap-slot shape))]
|
||||
|
||||
(log/dbg :hint "repairing shape :duplicated-slot" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes (map :id child-with-duplicate) repair-shape))))
|
||||
|
||||
|
||||
|
||||
(defmethod repair-error :component-duplicate-slot
|
||||
[_ {:keys [shape] :as error} file-data _]
|
||||
(let [main-shape (get-in shape [:objects (:main-instance-id shape)])
|
||||
childs (map #(get (:objects shape) %) (:shapes main-shape))
|
||||
childs-with-duplicate (let [result (reduce (fn [[seen duplicates] item]
|
||||
(let [swap-slot (ctk/get-swap-slot item)]
|
||||
(if (contains? seen swap-slot)
|
||||
[seen (conj duplicates item)]
|
||||
[(conj seen swap-slot) duplicates])))
|
||||
[#{} []]
|
||||
childs)]
|
||||
(second result))
|
||||
duplicated-ids (set (mapv :id childs-with-duplicate))
|
||||
repair-component
|
||||
(fn [component]
|
||||
(let [objects (reduce-kv (fn [acc k v]
|
||||
(if (contains? duplicated-ids k)
|
||||
(assoc acc k (ctk/remove-swap-slot v))
|
||||
(assoc acc k v)))
|
||||
{}
|
||||
(:objects component))]
|
||||
(assoc component :objects objects)))]
|
||||
|
||||
(log/dbg :hint "repairing component :component-duplicated-slot" :id (:id shape) :name (:name shape))
|
||||
(-> (pcb/empty-changes nil)
|
||||
(pcb/with-library-data file-data)
|
||||
(pcb/update-component (:id shape) repair-component))))
|
||||
|
||||
(defmethod repair-error :missing-slot
|
||||
[_ {:keys [shape page-id args] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
|
|
|
@ -31,9 +31,11 @@
|
|||
:child-not-found
|
||||
:frame-not-found
|
||||
:invalid-frame
|
||||
:component-duplicate-slot
|
||||
:component-not-main
|
||||
:component-main-external
|
||||
:component-not-found
|
||||
:duplicate-slot
|
||||
:invalid-main-instance-id
|
||||
:invalid-main-instance-page
|
||||
:invalid-main-instance
|
||||
|
@ -64,7 +66,7 @@
|
|||
[:shape {:optional true} :map] ; Cannot validate a shape because here it may be broken
|
||||
[:shape-id {:optional true} ::sm/uuid]
|
||||
[:file-id ::sm/uuid]
|
||||
[:page-id ::sm/uuid]]))
|
||||
[:page-id {:optional true} [:maybe ::sm/uuid]]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; ERROR HANDLING
|
||||
|
@ -296,6 +298,22 @@
|
|||
"This shape should not have swap slot"
|
||||
shape file page)))
|
||||
|
||||
(defn- has-duplicate-swap-slot?
|
||||
[shape container]
|
||||
(let [shapes (map #(get (:objects container) %) (:shapes shape))
|
||||
slots (->> (map #(ctk/get-swap-slot %) shapes)
|
||||
(remove nil?))
|
||||
counts (frequencies slots)]
|
||||
(some (fn [[_ count]] (> count 1)) counts)))
|
||||
|
||||
(defn- check-duplicate-swap-slot
|
||||
"Validate that the children of this shape does not have duplicated slots."
|
||||
[shape file page]
|
||||
(when (has-duplicate-swap-slot? shape page)
|
||||
(report-error :duplicate-slot
|
||||
"This shape has children with the same swap slot"
|
||||
shape file page)))
|
||||
|
||||
(defn- check-shape-main-root-top
|
||||
"Root shape of a top main instance:
|
||||
|
||||
|
@ -308,6 +326,7 @@
|
|||
(check-component-root shape file page)
|
||||
(check-component-not-ref shape file page)
|
||||
(check-empty-swap-slot shape file page)
|
||||
(check-duplicate-swap-slot shape file page)
|
||||
(run! #(check-shape % file page libraries :context :main-top) (:shapes shape)))
|
||||
|
||||
(defn- check-shape-main-root-nested
|
||||
|
@ -335,6 +354,7 @@
|
|||
(check-component-root shape file page)
|
||||
(check-component-ref shape file page libraries)
|
||||
(check-empty-swap-slot shape file page)
|
||||
(check-duplicate-swap-slot shape file page)
|
||||
(run! #(check-shape % file page libraries :context :copy-top :library-exists library-exists) (:shapes shape))))
|
||||
|
||||
(defn- check-shape-copy-root-nested
|
||||
|
@ -453,13 +473,24 @@
|
|||
shape file page)
|
||||
(check-shape-not-component shape file page libraries))))))))
|
||||
|
||||
(defn check-component-duplicate-swap-slot
|
||||
[component file]
|
||||
(let [shape (get-in component [:objects (:main-instance-id component)])]
|
||||
(when (has-duplicate-swap-slot? shape component)
|
||||
(report-error :component-duplicate-slot
|
||||
"This deleted component has children with the same swap slot"
|
||||
component file nil))))
|
||||
|
||||
|
||||
(defn- check-component
|
||||
"Validate semantic coherence of a component. Report all errors found."
|
||||
[component file]
|
||||
(when (and (contains? component :objects) (nil? (:objects component)))
|
||||
(report-error :component-nil-objects-not-allowed
|
||||
"Objects list cannot be nil"
|
||||
component file nil)))
|
||||
component file nil))
|
||||
(when (:deleted component)
|
||||
(check-component-duplicate-swap-slot component file)))
|
||||
|
||||
(defn- get-orphan-shapes
|
||||
[{:keys [objects] :as page}]
|
||||
|
|
2
frontend/.gitignore
vendored
2
frontend/.gitignore
vendored
|
@ -10,3 +10,5 @@ node_modules/
|
|||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
visual-dashboard.spec.js-snapshots
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"~:id": "~u62edaeb8-e212-81ca-8004-80a6f8a42e8e",
|
||||
"~:profile-id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:created-at": "~m1718348381840",
|
||||
"~:updated-at": "~m1718348381840",
|
||||
"~:name": "new token",
|
||||
"~:token": "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0.9aFN5YdOI-b-NQPos5uqF8J8b9iMyeri3yYhV5FlHuhNbRwk0YuftA.Dygx9O5-KsAHpuqD.ryTDCqelYOk1XYflTlDGFlzG8VLuElKHSGHdJyJvWqcCUANWzl8cVvezvU2GWg1Piin21KNrcV0TEcHPpDggySRbTn01MOIjw3vTVHdGrlHaVq5VpnWb5hCfs_P9kF7Y2IWOa4da4mM.IulvBQUllnay7clORd-NSg"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"~:id": "~u62edaeb8-e212-81ca-8004-80a6f8a42e8e",
|
||||
"~:name": "new token",
|
||||
"~:created-at": "~m1718348381840",
|
||||
"~:updated-at": "~m1718348381840"
|
||||
}
|
||||
]
|
15
frontend/playwright/data/dashboard/get-font-variants.json
Normal file
15
frontend/playwright/data/dashboard/get-font-variants.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
[
|
||||
{
|
||||
"~:font-style": "normal",
|
||||
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:font-id": "~u838cda51-c50f-8032-8004-6ac92ea6eaea",
|
||||
"~:font-weight": 400,
|
||||
"~:ttf-file-id": "~ue3710e43-7e40-405d-a4ea-8bb85443d44b",
|
||||
"~:modified-at": "~m1716880956479",
|
||||
"~:otf-file-id": "~u72bd3cda-478a-4e0e-a372-4a4f7cdc1371",
|
||||
"~:id": "~u28f4b65f-3667-8087-8004-6ac93050433a",
|
||||
"~:woff1-file-id": "~ua4c0a056-2eb6-47cc-bf80-3115d14e048d",
|
||||
"~:created-at": "~m1716880956479",
|
||||
"~:font-family": "Milligram Variable Trial"
|
||||
}
|
||||
]
|
19
frontend/playwright/data/dashboard/get-projects-full.json
Normal file
19
frontend/playwright/data/dashboard/get-projects-full.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
[{
|
||||
"~:id": "~ue5a24d1b-ef1e-812f-8004-52bab84be6f7",
|
||||
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:created-at": "~m1715266551088",
|
||||
"~:modified-at": "~m1715266551088",
|
||||
"~:is-default": false,
|
||||
"~:name": "New Project 1",
|
||||
"~:is-pinned": false,
|
||||
"~:count": 1
|
||||
},
|
||||
{
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:created-at": "~m1713533116382",
|
||||
"~:modified-at": "~m1713873823633",
|
||||
"~:is-default": true,
|
||||
"~:name": "Drafts",
|
||||
"~:count": 1
|
||||
}]
|
|
@ -0,0 +1 @@
|
|||
[]
|
219
frontend/playwright/data/dashboard/get-shared-files.json
Normal file
219
frontend/playwright/data/dashboard/get-shared-files.json
Normal file
|
@ -0,0 +1,219 @@
|
|||
{
|
||||
"~#set": [
|
||||
{
|
||||
"~:name": "New File 3",
|
||||
"~:revn": 1,
|
||||
"~:id": "~u28f4b65f-3667-8087-8004-69eca173cc07",
|
||||
"~:is-shared": true,
|
||||
"~:project-id": "~ue5a24d1b-ef1e-812f-8004-52bab84be6f7",
|
||||
"~:created-at": "~m1713518796912",
|
||||
"~:modified-at": "~m1713519762931",
|
||||
"~:library-summary": {
|
||||
"~:components": {
|
||||
"~:count": 1,
|
||||
"~:sample": [
|
||||
{
|
||||
"~:id": "~ua30724ae-f8d8-8003-8004-69ecacfc8a4c",
|
||||
"~:name": "Rectangle",
|
||||
"~:path": "",
|
||||
"~:modified-at": "~m1716823150739",
|
||||
"~:main-instance-id": "~ua30724ae-f8d8-8003-8004-69ecacfa2045",
|
||||
"~:main-instance-page": "~u28f4b65f-3667-8087-8004-69eca173cc08",
|
||||
"~:objects": {
|
||||
"~ua30724ae-f8d8-8003-8004-69ecacfa2045": {
|
||||
"~#shape": {
|
||||
"~:y": 168,
|
||||
"~: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,
|
||||
"~:hide-in-viewer": true,
|
||||
"~:name": "Rectangle",
|
||||
"~:width": 553,
|
||||
"~:type": "~:frame",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 481,
|
||||
"~:y": 168
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1034,
|
||||
"~:y": 168
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1034,
|
||||
"~:y": 550
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 481,
|
||||
"~:y": 550
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:component-root": true,
|
||||
"~:show-content": true,
|
||||
"~: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": "~ua30724ae-f8d8-8003-8004-69ecacfa2045",
|
||||
"~:parent-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:component-id": "~ua30724ae-f8d8-8003-8004-69ecacfc8a4c",
|
||||
"~:frame-id": "~u00000000-0000-0000-0000-000000000000",
|
||||
"~:strokes": [],
|
||||
"~:x": 481,
|
||||
"~:main-instance": true,
|
||||
"~:proportion": 1,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 481,
|
||||
"~:y": 168,
|
||||
"~:width": 553,
|
||||
"~:height": 382,
|
||||
"~:x1": 481,
|
||||
"~:y1": 168,
|
||||
"~:x2": 1034,
|
||||
"~:y2": 550
|
||||
}
|
||||
},
|
||||
"~:fills": [],
|
||||
"~:flip-x": null,
|
||||
"~:height": 382,
|
||||
"~:component-file": "~u28f4b65f-3667-8087-8004-69eca173cc07",
|
||||
"~:flip-y": null,
|
||||
"~:shapes": [
|
||||
"~ua30724ae-f8d8-8003-8004-69eca9b27c8c"
|
||||
]
|
||||
}
|
||||
},
|
||||
"~ua30724ae-f8d8-8003-8004-69eca9b27c8c": {
|
||||
"~#shape": {
|
||||
"~:y": 168,
|
||||
"~: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": 553,
|
||||
"~:type": "~:rect",
|
||||
"~:points": [
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 481,
|
||||
"~:y": 168
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1034,
|
||||
"~:y": 168
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 1034,
|
||||
"~:y": 550
|
||||
}
|
||||
},
|
||||
{
|
||||
"~#point": {
|
||||
"~:x": 481,
|
||||
"~:y": 550
|
||||
}
|
||||
}
|
||||
],
|
||||
"~:proportion-lock": false,
|
||||
"~:transform-inverse": {
|
||||
"~#matrix": {
|
||||
"~:a": 1.0,
|
||||
"~:b": 0.0,
|
||||
"~:c": 0.0,
|
||||
"~:d": 1.0,
|
||||
"~:e": 0.0,
|
||||
"~:f": 0.0
|
||||
}
|
||||
},
|
||||
"~:constraints-v": "~:scale",
|
||||
"~:constraints-h": "~:scale",
|
||||
"~:id": "~ua30724ae-f8d8-8003-8004-69eca9b27c8c",
|
||||
"~:parent-id": "~ua30724ae-f8d8-8003-8004-69ecacfa2045",
|
||||
"~:frame-id": "~ua30724ae-f8d8-8003-8004-69ecacfa2045",
|
||||
"~:strokes": [],
|
||||
"~:x": 481,
|
||||
"~:proportion": 1,
|
||||
"~:selrect": {
|
||||
"~#rect": {
|
||||
"~:x": 481,
|
||||
"~:y": 168,
|
||||
"~:width": 553,
|
||||
"~:height": 382,
|
||||
"~:x1": 481,
|
||||
"~:y1": 168,
|
||||
"~:x2": 1034,
|
||||
"~:y2": 550
|
||||
}
|
||||
},
|
||||
"~:fills": [
|
||||
{
|
||||
"~:fill-color": "#B1B2B5",
|
||||
"~:fill-opacity": 1
|
||||
}
|
||||
],
|
||||
"~:flip-x": null,
|
||||
"~:ry": 0,
|
||||
"~:height": 382,
|
||||
"~:flip-y": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"~:media": {
|
||||
"~:count": 0,
|
||||
"~:sample": []
|
||||
},
|
||||
"~:colors": {
|
||||
"~:count": 0,
|
||||
"~:sample": []
|
||||
},
|
||||
"~:typographies": {
|
||||
"~:count": 0,
|
||||
"~:sample": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -0,0 +1,6 @@
|
|||
[
|
||||
{ "~:email": "test1@mail.com", "~:role": "~:editor", "~:expired": true },
|
||||
{ "~:email": "test2@mail.com", "~:role": "~:editor", "~:expired": false },
|
||||
{ "~:email": "test3@mail.com", "~:role": "~:admin", "~:expired": true },
|
||||
{ "~:email": "test4@mail.com", "~:role": "~:admin", "~:expired": false }
|
||||
]
|
16
frontend/playwright/data/dashboard/get-team-members.json
Normal file
16
frontend/playwright/data/dashboard/get-team-members.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
[
|
||||
{
|
||||
"~:is-admin": true,
|
||||
"~:email": "foo@example.com",
|
||||
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
|
||||
"~:name": "Princesa Leia",
|
||||
"~:fullname": "Princesa Leia",
|
||||
"~:is-owner": false,
|
||||
"~:modified-at": "~m1713533116365",
|
||||
"~:can-edit": true,
|
||||
"~:is-active": true,
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b",
|
||||
"~:created-at": "~m1713533116365"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,29 @@
|
|||
[
|
||||
{
|
||||
"~:id": "~u8b479b80-e02d-8074-8004-4088dc6bfd11",
|
||||
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:created-at": "~m1714045521389",
|
||||
"~:modified-at": "~m1714045654874",
|
||||
"~:name": "New File 2",
|
||||
"~:revn": 1,
|
||||
"~:is-shared": false
|
||||
},
|
||||
{
|
||||
"~:id": "~u95d6fdd8-48d8-8148-8004-38af910d2dbe",
|
||||
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:created-at": "~m1713518796912",
|
||||
"~:modified-at": "~m1713519762931",
|
||||
"~:name": "New File 1",
|
||||
"~:revn": 1,
|
||||
"~:is-shared": false
|
||||
},
|
||||
{
|
||||
"~:id": "~u28f4b65f-3667-8087-8004-69eca173cc07",
|
||||
"~:project-id": "~ue5a24d1b-ef1e-812f-8004-52bab84be6f7",
|
||||
"~:created-at": "~m1713518796912",
|
||||
"~:modified-at": "~m1713519762931",
|
||||
"~:name": "New File 3",
|
||||
"~:revn": 1,
|
||||
"~:is-shared": true
|
||||
}
|
||||
]
|
1
frontend/playwright/data/dashboard/get-team-stats.json
Normal file
1
frontend/playwright/data/dashboard/get-team-stats.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"~:projects":1,"~:files":3}
|
|
@ -0,0 +1 @@
|
|||
[]
|
20
frontend/playwright/data/dashboard/get-webhooks.json
Normal file
20
frontend/playwright/data/dashboard/get-webhooks.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
[
|
||||
{
|
||||
"~:id": "~u29ce7ec9-e75d-81b4-8004-08100373558a",
|
||||
"~:uri": {
|
||||
"~#uri": "https://www.abc.es"
|
||||
},
|
||||
"~:mtype": "application/json",
|
||||
"~:is-active": false,
|
||||
"~:error-count": 0
|
||||
},
|
||||
{
|
||||
"~:id": "~u43d6b3b1-40f7-807b-8003-f9846292b4c7",
|
||||
"~:uri": {
|
||||
"~#uri": "https://www.google.com"
|
||||
},
|
||||
"~:mtype": "application/json",
|
||||
"~:is-active": true,
|
||||
"~:error-count": 0
|
||||
}
|
||||
]
|
29
frontend/playwright/data/dashboard/search-files.json
Normal file
29
frontend/playwright/data/dashboard/search-files.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
[
|
||||
{
|
||||
"~:id": "~u8b479b80-e02d-8074-8004-4088dc6bfd11",
|
||||
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:created-at": "~m1714045521389",
|
||||
"~:modified-at": "~m1714045654874",
|
||||
"~:name": "New File 2",
|
||||
"~:revn": 1,
|
||||
"~:is-shared": false
|
||||
},
|
||||
{
|
||||
"~:id": "~u95d6fdd8-48d8-8148-8004-38af910d2dbe",
|
||||
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:created-at": "~m1713518796912",
|
||||
"~:modified-at": "~m1713519762931",
|
||||
"~:name": "New File 1",
|
||||
"~:revn": 1,
|
||||
"~:is-shared": false
|
||||
},
|
||||
{
|
||||
"~:id": "~u28f4b65f-3667-8087-8004-69eca173cc07",
|
||||
"~:project-id": "~ue5a24d1b-ef1e-812f-8004-52bab84be6f7",
|
||||
"~:created-at": "~m1713518796912",
|
||||
"~:modified-at": "~m1713519762931",
|
||||
"~:name": "New File 3",
|
||||
"~:revn": 1,
|
||||
"~:is-shared": true
|
||||
}
|
||||
]
|
|
@ -0,0 +1,48 @@
|
|||
[
|
||||
{
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"layout/grid",
|
||||
"styles/v2",
|
||||
"fdata/pointer-map",
|
||||
"fdata/objects-map",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type"
|
||||
]
|
||||
},
|
||||
"~:permissions": {
|
||||
"~:type": "~:membership",
|
||||
"~:is-owner": true,
|
||||
"~:is-admin": true,
|
||||
"~:can-edit": true
|
||||
},
|
||||
"~:name": "Default",
|
||||
"~:modified-at": "~m1713533116375",
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:created-at": "~m1713533116375",
|
||||
"~:is-default": true
|
||||
},
|
||||
{
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"layout/grid",
|
||||
"styles/v2",
|
||||
"fdata/pointer-map",
|
||||
"fdata/objects-map",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type"
|
||||
]
|
||||
},
|
||||
"~:permissions": {
|
||||
"~:type": "~:membership",
|
||||
"~:is-owner": true,
|
||||
"~:is-admin": true,
|
||||
"~:can-edit": true
|
||||
},
|
||||
"~:name": "Second team",
|
||||
"~:modified-at": "~m1701164272671",
|
||||
"~:id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
|
||||
"~:created-at": "~m1701164272671",
|
||||
"~:is-default": false
|
||||
}
|
||||
]
|
|
@ -50,6 +50,8 @@ export class DashboardPage extends BaseWebSocketPage {
|
|||
|
||||
static anyTeamId = "c7ce0794-0992-8105-8004-38e630f40f6d";
|
||||
|
||||
static secondTeamId = "dd33ff88-f4e5-8033-8003-8096cc07bdf3";
|
||||
|
||||
static draftProjectId = "c7ce0794-0992-8105-8004-38e630f7920b";
|
||||
|
||||
constructor(page) {
|
||||
|
@ -60,12 +62,34 @@ export class DashboardPage extends BaseWebSocketPage {
|
|||
this.draftTitle = page.getByRole("heading", { name: "Drafts" });
|
||||
this.draftLink = page.getByTestId("drafts-link-sidebar");
|
||||
this.draftsFile = page.getByText(/New File 1/);
|
||||
this.fontsLink = page.getByTestId("fonts-link-sidebar");
|
||||
this.fontsTitle = page.getByRole("heading", { name: "Fonts", level: 1 });
|
||||
this.libsLink = page.getByTestId("libs-link-sidebar");
|
||||
this.libsTitle = page.getByRole("heading", { name: "Libraries", level: 1 });
|
||||
this.searchButton = page.getByRole("button", { name: "dashboard-search" });
|
||||
this.searchTitle = page.getByRole("heading", { name: "Search results" });
|
||||
this.searchInput = page.getByPlaceholder('Search…');
|
||||
this.newFileName = page.getByText("New File 3");
|
||||
this.teamDropdown = page.getByRole('button', { name: 'Your Penpot' });
|
||||
this.userAccount = page.getByRole('button', { name: "Princesa Leia Princesa Leia" });
|
||||
this.userProfileOption = page.getByText("Your account");
|
||||
this.userAccountTitle = page.getByRole("heading", {name: "Your account"});
|
||||
}
|
||||
|
||||
async setupDraftsEmpty() {
|
||||
await this.mockRPC("get-project-files?project-id=*", "dashboard/get-project-files-empty.json");
|
||||
}
|
||||
|
||||
async setupSearchEmpty() {
|
||||
await this.mockRPC("search-files", "dashboard/search-files-empty.json", {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
async setupLibrariesEmpty() {
|
||||
await this.mockRPC("get-team-shared-files?team-id=*", "dashboard/get-shared-files-empty.json");
|
||||
}
|
||||
|
||||
async setupDrafts() {
|
||||
await this.mockRPC("get-project-files?project-id=*", "dashboard/get-project-files.json");
|
||||
}
|
||||
|
@ -74,15 +98,95 @@ export class DashboardPage extends BaseWebSocketPage {
|
|||
await this.mockRPC("create-project", "dashboard/create-project.json", { method: "POST" });
|
||||
await this.mockRPC("get-projects?team-id=*", "dashboard/get-projects-new.json");
|
||||
}
|
||||
async goToWorkspace() {
|
||||
|
||||
async setupDashboardFull() {
|
||||
await this.mockRPC("get-projects?team-id=*", "dashboard/get-projects-full.json");
|
||||
await this.mockRPC("get-project-files?project-id=*", "dashboard/get-project-files.json");
|
||||
await this.mockRPC("get-team-shared-files?team-id=*", "dashboard/get-shared-files.json");
|
||||
await this.mockRPC("get-team-shared-files?project-id=*", "dashboard/get-shared-files.json");
|
||||
await this.mockRPC("get-team-recent-files?team-id=*", "dashboard/get-team-recent-files.json");
|
||||
await this.mockRPC("get-font-variants?team-id=*", "dashboard/get-font-variants.json");
|
||||
await this.mockRPC("search-files", "dashboard/search-files.json", { method: "POST" });
|
||||
await this.mockRPC("search-files", "dashboard/search-files.json" );
|
||||
await this.mockRPC("get-teams", "logged-in-user/get-teams-complete.json");
|
||||
}
|
||||
|
||||
async setupAccessTokensEmpty() {
|
||||
await this.mockRPC("get-access-tokens", "dashboard/get-access-tokens-empty.json");
|
||||
}
|
||||
|
||||
async createAccessToken() {
|
||||
await this.mockRPC("create-access-token", "dashboard/create-access-token.json", { method: "POST" });
|
||||
}
|
||||
|
||||
async setupAccessTokens() {
|
||||
await this.mockRPC("get-access-tokens", "dashboard/get-access-tokens.json");
|
||||
}
|
||||
|
||||
async setupTeamInvitationsEmpty() {
|
||||
await this.mockRPC("get-team-invitations?team-id=*", "dashboard/get-team-invitations-empty.json");
|
||||
}
|
||||
|
||||
async setupTeamInvitations() {
|
||||
await this.mockRPC("get-team-invitations?team-id=*", "dashboard/get-team-invitations.json");
|
||||
}
|
||||
|
||||
async setupTeamWebhooksEmpty() {
|
||||
await this.mockRPC("get-webhooks?team-id=*", "dashboard/get-webhooks-empty.json");
|
||||
}
|
||||
|
||||
async setupTeamWebhooks() {
|
||||
await this.mockRPC("get-webhooks?team-id=*", "dashboard/get-webhooks.json");
|
||||
}
|
||||
|
||||
async setupTeamSettings() {
|
||||
await this.mockRPC("get-team-stats?team-id=*", "dashboard/get-team-stats.json");
|
||||
}
|
||||
|
||||
async goToDashboard() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.anyTeamId}/projects`);
|
||||
}
|
||||
|
||||
async goToSecondTeamDashboard() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.secondTeamId}/projects`);
|
||||
}
|
||||
|
||||
async goToSecondTeamMembersSection() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.secondTeamId}/members`);
|
||||
}
|
||||
|
||||
async goToSecondTeamInvitationsSection() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.secondTeamId}/invitations`);
|
||||
}
|
||||
|
||||
async goToSecondTeamWebhooksSection() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.secondTeamId}/webhooks`);
|
||||
}
|
||||
|
||||
async goToSecondTeamWebhooksSection() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.secondTeamId}/webhooks`);
|
||||
}
|
||||
|
||||
async goToSecondTeamSettingsSection() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.secondTeamId}/settings`);
|
||||
}
|
||||
|
||||
async goToSearch() {
|
||||
await this.page.goto(`#/dashboard/team/${DashboardPage.anyTeamId}/search`);
|
||||
}
|
||||
|
||||
async goToDrafts() {
|
||||
await this.page.goto(
|
||||
`#/dashboard/team/${DashboardPage.anyTeamId}/projects/${DashboardPage.draftProjectId}`,
|
||||
);
|
||||
}
|
||||
|
||||
async goToAccount() {
|
||||
|
||||
await this.userAccount.click();
|
||||
|
||||
await this.userProfileOption.click();
|
||||
}
|
||||
}
|
||||
|
||||
export default DashboardPage;
|
||||
|
|
|
@ -13,7 +13,7 @@ test.beforeEach(async ({ page }) => {
|
|||
test("Dashboad page has title ", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToWorkspace();
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await expect(dashboardPage.page).toHaveURL(/dashboard/);
|
||||
await expect(dashboardPage.titleLabel).toBeVisible();
|
||||
|
@ -23,7 +23,7 @@ test("User can create a new project", async ({ page }) => {
|
|||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupNewProject();
|
||||
|
||||
await dashboardPage.goToWorkspace();
|
||||
await dashboardPage.goToDashboard();
|
||||
await dashboardPage.addProjectBtn.click();
|
||||
|
||||
await expect(dashboardPage.projectName).toBeVisible();
|
||||
|
@ -33,7 +33,7 @@ test("User goes to draft page", async ({ page }) => {
|
|||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDraftsEmpty();
|
||||
|
||||
await dashboardPage.goToWorkspace();
|
||||
await dashboardPage.goToDashboard();
|
||||
await dashboardPage.draftLink.click();
|
||||
|
||||
await expect(dashboardPage.draftTitle).toBeVisible();
|
||||
|
|
|
@ -12,7 +12,7 @@ test("User can complete the onboarding", async ({ page }) => {
|
|||
const dashboardPage = new DashboardPage(page);
|
||||
const onboardingPage = new OnboardingPage(page);
|
||||
|
||||
await dashboardPage.goToWorkspace();
|
||||
await dashboardPage.goToDashboard();
|
||||
await expect(page.getByRole("heading", { name: "Help us get to know you" })).toBeVisible();
|
||||
|
||||
await onboardingPage.fillOnboardingInputsStep1();
|
||||
|
|
423
frontend/playwright/ui/visual-specs/visual-dashboard.spec.js
Normal file
423
frontend/playwright/ui/visual-specs/visual-dashboard.spec.js
Normal file
|
@ -0,0 +1,423 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
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("User goes to an empty dashboard", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await expect(dashboardPage.titleLabel).toBeVisible();
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
// Empty dashboard pages
|
||||
|
||||
test("User goes to an empty draft page", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDraftsEmpty();
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
await dashboardPage.draftLink.click();
|
||||
|
||||
await expect(dashboardPage.draftTitle).toBeVisible();
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to an empty fonts page", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
await dashboardPage.fontsLink.click();
|
||||
|
||||
await expect(dashboardPage.fontsTitle).toBeVisible();
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to an empty libraries page", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupLibrariesEmpty();
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
await dashboardPage.libsLink.click();
|
||||
|
||||
await expect(dashboardPage.libsTitle).toBeVisible();
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to an empty search page", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupSearchEmpty();
|
||||
|
||||
await dashboardPage.goToSearch();
|
||||
|
||||
await expect(dashboardPage.searchTitle).toBeVisible();
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to the dashboard with a new project", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupNewProject();
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await expect(dashboardPage.projectName).toBeVisible();
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
// Dashboard pages with content
|
||||
|
||||
test("User goes to a full dashboard", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await expect(dashboardPage.draftsFile).toBeVisible();
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to an full draft page", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
await dashboardPage.draftLink.click();
|
||||
|
||||
await expect(dashboardPage.draftTitle).toBeVisible();
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to an full library page", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
await dashboardPage.libsLink.click();
|
||||
|
||||
await expect(dashboardPage.libsTitle).toBeVisible();
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to an full fonts page", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
await dashboardPage.fontsLink.click();
|
||||
|
||||
await expect(dashboardPage.fontsTitle).toBeVisible();
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to an full search page", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
|
||||
await dashboardPage.goToSearch();
|
||||
|
||||
await expect(dashboardPage.searchInput).toBeVisible();
|
||||
|
||||
await dashboardPage.searchInput.fill("New");
|
||||
|
||||
await expect(dashboardPage.searchTitle).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.newFileName).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
// Account management
|
||||
|
||||
test("User opens user account", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await expect(dashboardPage.userAccount).toBeVisible();
|
||||
|
||||
await dashboardPage.goToAccount();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to user profile", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await dashboardPage.goToAccount();
|
||||
|
||||
await expect(dashboardPage.userAccountTitle).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to password management section", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await dashboardPage.goToAccount();
|
||||
|
||||
await page.getByText("Password").click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Change Password" })).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to settings section", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await dashboardPage.goToAccount();
|
||||
|
||||
await page.getByTestId("settings-profile").click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Settings" })).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to an empty access tokens secction", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await dashboardPage.setupAccessTokensEmpty();
|
||||
|
||||
await dashboardPage.goToAccount();
|
||||
|
||||
await page.getByText("Access tokens").click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Personal access tokens" })).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User can create an access token", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await dashboardPage.setupAccessTokensEmpty();
|
||||
|
||||
await dashboardPage.goToAccount();
|
||||
|
||||
await page.getByText("Access tokens").click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Personal access tokens" })).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Generate New Token" }).click();
|
||||
|
||||
await dashboardPage.createAccessToken();
|
||||
|
||||
await expect(page.getByPlaceholder("The name can help to know")).toBeVisible();
|
||||
|
||||
await page.getByPlaceholder("The name can help to know").fill("New token");
|
||||
|
||||
await expect(page.getByRole("button", { name: "Create token" })).not.toBeDisabled();
|
||||
|
||||
await page.getByRole("button", { name: "Create token" }).click();
|
||||
|
||||
await expect(page.getByRole("button", { name: "Create token" })).not.toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to a full access tokens secction", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await dashboardPage.setupAccessTokens();
|
||||
|
||||
await dashboardPage.goToAccount();
|
||||
|
||||
await page.getByText("Access tokens").click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Personal access tokens" })).toBeVisible();
|
||||
|
||||
await expect(page.getByText("new token", { exact: true })).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to the feedback secction", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await dashboardPage.goToAccount();
|
||||
|
||||
await page.getByText("Give feedback").click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Email" })).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
// Teams management
|
||||
|
||||
test("User opens teams selector with only one team", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await expect(dashboardPage.titleLabel).toBeVisible();
|
||||
|
||||
await dashboardPage.teamDropdown.click();
|
||||
|
||||
await expect(page.getByText("Create new team")).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User opens teams selector with more than one team", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await expect(dashboardPage.titleLabel).toBeVisible();
|
||||
|
||||
await dashboardPage.teamDropdown.click();
|
||||
|
||||
await expect(page.getByText("Second Team")).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to second team", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
|
||||
await dashboardPage.goToDashboard();
|
||||
|
||||
await dashboardPage.teamDropdown.click();
|
||||
|
||||
await expect(page.getByText("Second Team")).toBeVisible();
|
||||
|
||||
await page.getByText("Second Team").click();
|
||||
|
||||
await expect(page.getByText("Team Up")).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User opens team management dropdown", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
|
||||
await dashboardPage.goToSecondTeamDashboard();
|
||||
|
||||
await expect(page.getByText("Team Up")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "team-management" }).click();
|
||||
|
||||
await expect(page.getByTestId("team-members")).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to team management section", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
|
||||
await dashboardPage.goToSecondTeamMembersSection();
|
||||
|
||||
await expect(page.getByText("role")).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to an empty invitations section", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
await dashboardPage.setupTeamInvitationsEmpty();
|
||||
|
||||
await dashboardPage.goToSecondTeamInvitationsSection();
|
||||
|
||||
await expect(page.getByText("No pending invitations")).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to a complete invitations section", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
await dashboardPage.setupTeamInvitations();
|
||||
|
||||
await dashboardPage.goToSecondTeamInvitationsSection();
|
||||
|
||||
await expect(page.getByText("test1@mail.com")).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
|
||||
test("User invite people to the team", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
await dashboardPage.setupTeamInvitationsEmpty();
|
||||
|
||||
await dashboardPage.goToSecondTeamInvitationsSection();
|
||||
|
||||
await expect(page.getByTestId("invite-member")).toBeVisible();
|
||||
|
||||
await page.getByTestId("invite-member").click();
|
||||
|
||||
await expect(page.getByText("Invite with the role")).toBeVisible();
|
||||
|
||||
await page.getByPlaceholder('Emails, comma separated').fill("test5@mail.com");
|
||||
|
||||
await expect(page.getByText("Send invitation")).not.toBeDisabled();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to an empty webhook section", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
await dashboardPage.setupTeamWebhooksEmpty();
|
||||
|
||||
await dashboardPage.goToSecondTeamWebhooksSection();
|
||||
|
||||
await expect(page.getByText("No webhooks created so far.")).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to a complete webhook section", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
await dashboardPage.setupTeamWebhooks();
|
||||
|
||||
await dashboardPage.goToSecondTeamWebhooksSection();
|
||||
|
||||
await expect(page.getByText("https://www.google.com")).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
||||
|
||||
test("User goes to the team settings section", async ({ page }) => {
|
||||
const dashboardPage = new DashboardPage(page);
|
||||
await dashboardPage.setupDashboardFull();
|
||||
await dashboardPage.setupTeamSettings();
|
||||
|
||||
await dashboardPage.goToSecondTeamSettingsSection();
|
||||
|
||||
await expect(page.getByText("TEAM INFO")).toBeVisible();
|
||||
|
||||
await expect(dashboardPage.page).toHaveScreenshot();
|
||||
});
|
|
@ -590,9 +590,6 @@
|
|||
width: 100%;
|
||||
z-index: $z-index-modal;
|
||||
background-color: var(--overlay-color);
|
||||
&.onboarding-a-b-test {
|
||||
background-color: var(--overlay-color-onboarding-a-b-test);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-container-base {
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
// Dark background
|
||||
--db-primary: #18181a;
|
||||
--db-primary-60: #{color.change(#18181a, $alpha: 0.6)};
|
||||
--db-primary-90: #{color.change(#18181a, $alpha: 0.9)};
|
||||
--db-secondary: #000000;
|
||||
--db-secondary-30: #{color.change(#000000, $alpha: 0.3)};
|
||||
--db-secondary-80: #{color.change(#000000, $alpha: 0.8)};
|
||||
|
@ -36,7 +35,6 @@
|
|||
// Light background
|
||||
--lb-primary: #ffffff;
|
||||
--lb-primary-60: #{color.change(#ffffff, $alpha: 0.6)};
|
||||
--lb-primary-90: #{color.change(#ffffff, $alpha: 0.9)};
|
||||
--lb-secondary: #e8eaee;
|
||||
--lb-secondary-30: #{color.change(#e8eaee, $alpha: 0.3)};
|
||||
--lb-secondary-80: #{color.change(#e8eaee, $alpha: 0.8)};
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
--color-info-foreground: var(--status-color-info-500);
|
||||
|
||||
--overlay-color: var(--db-primary-60);
|
||||
--overlay-color-onboarding-a-b-test: var(--db-primary-90);
|
||||
|
||||
--shadow-color: var(--db-secondary-30);
|
||||
--radio-button-box-shadow: 0 0 0 1px var(--db-secondary-30) inset;
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
--color-info-foreground: var(--status-color-info-500);
|
||||
|
||||
--overlay-color: var(--lb-primary-60);
|
||||
--overlay-color-onboarding-a-b-test: var(--lb-primary-90);
|
||||
|
||||
--shadow-color: var(--lf-secondary-40);
|
||||
--radio-button-box-shadow: 0 0 0 1px var(--lb-secondary) inset;
|
||||
|
|
|
@ -35,6 +35,7 @@ async function compileSass(path) {
|
|||
log.info("done:", `(${ppt(end)})`);
|
||||
}
|
||||
|
||||
await fs.mkdir("./resources/public/css/", { recursive: true });
|
||||
await compileSassAll();
|
||||
await h.copyAssets()
|
||||
await h.compileSvgSprites()
|
||||
|
|
|
@ -256,11 +256,13 @@
|
|||
(if (or @focused? (seq search-term))
|
||||
[:button {:class (stl/css :search-btn :clear-search-btn)
|
||||
:tab-index "0"
|
||||
:aria-label "dashboard-clear-search"
|
||||
:on-click on-clear-click
|
||||
:on-key-down handle-clear-search}
|
||||
clear-search-icon]
|
||||
|
||||
[:button {:class (stl/css :search-btn)
|
||||
:aria-label "dashboard-search"
|
||||
:on-click on-clear-click}
|
||||
search-icon])]))
|
||||
|
||||
|
@ -504,11 +506,13 @@
|
|||
:on-key-down handle-members
|
||||
:className (stl/css :team-options-item)
|
||||
:id "teams-options-members"
|
||||
:data-testid "team-members"
|
||||
:data-test "team-members"}
|
||||
(tr "labels.members")]
|
||||
[:> dropdown-menu-item* {:on-click go-invitations
|
||||
:on-key-down handle-invitations
|
||||
:className (stl/css :team-options-item)
|
||||
:data-testid "team-invitations"
|
||||
:id "teams-options-invitations"
|
||||
:data-test "team-invitations"}
|
||||
(tr "labels.invitations")]
|
||||
|
@ -524,6 +528,7 @@
|
|||
:on-key-down handle-settings
|
||||
:className (stl/css :team-options-item)
|
||||
:id "teams-options-settings"
|
||||
:data-testid "team-settings"
|
||||
:data-test "team-settings"}
|
||||
(tr "labels.settings")]
|
||||
|
||||
|
@ -533,6 +538,7 @@
|
|||
:on-key-down handle-rename
|
||||
:id "teams-options-rename"
|
||||
:className (stl/css :team-options-item)
|
||||
:data-testid "rename-team"
|
||||
:data-test "rename-team"}
|
||||
(tr "labels.rename")])
|
||||
|
||||
|
@ -550,6 +556,7 @@
|
|||
:on-key-down handle-leave-as-owner-clicked
|
||||
:id "teams-options-leave-team"
|
||||
:className (stl/css :team-options-item)
|
||||
:data-testid "leave-team"
|
||||
:data-test "leave-team"}
|
||||
(tr "dashboard.leave-team")]
|
||||
|
||||
|
@ -654,6 +661,7 @@
|
|||
(when-not (:is-default team)
|
||||
[:button {:class (stl/css :switch-options)
|
||||
:on-click handle-show-opts-click
|
||||
:aria-label "team-management"
|
||||
:tab-index "0"
|
||||
:on-key-down handle-show-opts-keydown}
|
||||
menu-icon])]
|
||||
|
@ -792,6 +800,7 @@
|
|||
[:li {:class (stl/css-case :current libs?
|
||||
:sidebar-nav-item true)}
|
||||
[:& link {:action go-libs
|
||||
:data-testid "libs-link-sidebar"
|
||||
:class (stl/css :sidebar-link)
|
||||
:keyboard-action go-libs-with-key}
|
||||
[:span {:class (stl/css :element-title)} (tr "labels.shared-libraries")]]]]]
|
||||
|
@ -803,6 +812,7 @@
|
|||
:current fonts?)}
|
||||
[:& link {:action go-fonts
|
||||
:class (stl/css :sidebar-link)
|
||||
:data-testid "fonts-link-sidebar"
|
||||
:keyboard-action go-fonts-with-key
|
||||
:data-test "fonts"}
|
||||
[:span {:class (stl/css :element-title)} (tr "labels.fonts")]]]]]
|
||||
|
@ -946,11 +956,11 @@
|
|||
:on-hide-comments handle-hide-comments}])
|
||||
|
||||
[:div {:class (stl/css :profile-section)}
|
||||
[:div {:class (stl/css :profile)
|
||||
:tab-index "0"
|
||||
:on-click handle-click
|
||||
:on-key-down handle-key-down
|
||||
:data-test "profile-btn"}
|
||||
[:button {:class (stl/css :profile)
|
||||
:tab-index "0"
|
||||
:on-click handle-click
|
||||
:on-key-down handle-key-down
|
||||
:data-test "profile-btn"}
|
||||
[:img {:src photo
|
||||
:class (stl/css :profile-img)
|
||||
:alt (:fullname profile)}]
|
||||
|
|
|
@ -331,10 +331,12 @@
|
|||
}
|
||||
|
||||
.profile {
|
||||
@include buttonStyle;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: $s-8;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.profile-fullname {
|
||||
|
|
|
@ -105,7 +105,8 @@
|
|||
[:a
|
||||
{:class (stl/css :btn-secondary :btn-small)
|
||||
:on-click on-invite-member
|
||||
:data-test "invite-member"}
|
||||
:data-test "invite-member"
|
||||
:data-testid "invite-member"}
|
||||
(tr "dashboard.invite-profile")]
|
||||
[:div {:class (stl/css :blank-space)}])]]))
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
(ns app.main.ui.onboarding.newsletter
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.config :as cf]
|
||||
[app.main.data.events :as-alias ev]
|
||||
[app.main.data.messages :as msg]
|
||||
[app.main.data.users :as du]
|
||||
|
@ -45,14 +44,10 @@
|
|||
(assoc :label "newsletter:subscriptions")
|
||||
(assoc :step 6))]
|
||||
(st/emit! (ptk/data-event ::ev/event params)
|
||||
(du/update-profile-props state)))))
|
||||
|
||||
onboarding-a-b-test?
|
||||
(cf/external-feature-flag "signup-background" "test")]
|
||||
(du/update-profile-props state)))))]
|
||||
|
||||
[:div {:class (stl/css-case
|
||||
:modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
:modal-overlay true)}
|
||||
|
||||
[:div.animated.fadeInDown {:class (stl/css :modal-container)}
|
||||
[:div {:class (stl/css :modal-left)}
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.config :as cf]
|
||||
[app.main.data.events :as-alias ev]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.store :as st]
|
||||
|
@ -497,14 +496,10 @@
|
|||
(fn [form]
|
||||
(let [data (merge @clean-data (:clean-data @form))]
|
||||
(reset! clean-data data)
|
||||
(st/emit! (du/mark-questions-as-answered data)))))
|
||||
|
||||
onboarding-a-b-test?
|
||||
(cf/external-feature-flag "signup-background" "test")]
|
||||
(st/emit! (du/mark-questions-as-answered data)))))]
|
||||
|
||||
[:div {:class (stl/css-case
|
||||
:modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
:modal-overlay true)}
|
||||
[:div {:class (stl/css :modal-container)
|
||||
:ref container}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.messages :as msg]
|
||||
|
@ -260,14 +259,10 @@
|
|||
on-back
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(swap! name* (constantly nil))))
|
||||
|
||||
onboarding-a-b-test?
|
||||
(cf/external-feature-flag "signup-background" "test")]
|
||||
(swap! name* (constantly nil))))]
|
||||
|
||||
[:div {:class (stl/css-case
|
||||
:modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
:modal-overlay true)}
|
||||
|
||||
[:div.animated.fadeIn {:class (stl/css :modal-container)}
|
||||
[:& left-sidebar]
|
||||
|
|
|
@ -8,203 +8,196 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.config :as cf]
|
||||
[app.main.ui.releases.common :as c]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
;; TODO: Review all copies and alt text
|
||||
(defmethod c/render-release-notes "2.0"
|
||||
[{:keys [slide klass next finish navigate version]}]
|
||||
(let [onboarding-a-b-test? (cf/external-feature-flag "signup-background" "test")]
|
||||
(mf/html
|
||||
(case slide
|
||||
:start
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-intro-image.png"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "A graphic illustration with Penpot style"}]
|
||||
(mf/html
|
||||
(case slide
|
||||
:start
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-intro-image.png"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "A graphic illustration with Penpot style"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"Welcome to Penpot 2.0! "]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"Welcome to Penpot 2.0! "]
|
||||
|
||||
[:div {:class (stl/css :version-tag)}
|
||||
(dm/str "Version " version)]]
|
||||
[:div {:class (stl/css :version-tag)}
|
||||
(dm/str "Version " version)]]
|
||||
|
||||
[:div {:class (stl/css :features-block)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"CSS Grid Layout: "]
|
||||
"Bring your designs to life, knowing that what you create is what developers code."]
|
||||
[:div {:class (stl/css :features-block)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"CSS Grid Layout: "]
|
||||
"Bring your designs to life, knowing that what you create is what developers code."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"Sleeker UI: "]
|
||||
"We’ve polished Penpot to make your experience smoother and more enjoyable."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"Sleeker UI: "]
|
||||
"We’ve polished Penpot to make your experience smoother and more enjoyable."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"New Components System: "]
|
||||
"Managing and using your design components got a whole lot better."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
[:spam {:class (stl/css :feature-title)}
|
||||
"New Components System: "]
|
||||
"Managing and using your design components got a whole lot better."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"And that’s not all - we’ve fined tuned performance and "
|
||||
"accessibility to give you a better and more fluid design experience."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"And that’s not all - we’ve fined tuned performance and "
|
||||
"accessibility to give you a better and more fluid design experience."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
" Ready to dive in? Let 's get started!"]]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
" Ready to dive in? Let 's get started!"]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:button {:class (stl/css :next-btn)
|
||||
:on-click next} "Continue"]]]]]]
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:button {:class (stl/css :next-btn)
|
||||
:on-click next} "Continue"]]]]]]
|
||||
|
||||
0
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-css-grid.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's CSS Grid Layout"}]
|
||||
0
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-css-grid.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's CSS Grid Layout"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"CSS Grid Layout - Design Meets Development"]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"CSS Grid Layout - Design Meets Development"]]
|
||||
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"The much-awaited Grid Layout introduces 2-dimensional"
|
||||
" layout capabilities to Penpot, allowing for the creation"
|
||||
" of adaptive layouts by leveraging the power of CSS properties."]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"The much-awaited Grid Layout introduces 2-dimensional"
|
||||
" layout capabilities to Penpot, allowing for the creation"
|
||||
" of adaptive layouts by leveraging the power of CSS properties."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"It’s a host of new features, including columns and"
|
||||
" rows management, flexible units such as FR (fractions),"
|
||||
" the ability to create and name areas, and tons of new "
|
||||
"and unique possibilities within a design tool."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"It’s a host of new features, including columns and"
|
||||
" rows management, flexible units such as FR (fractions),"
|
||||
" the ability to create and name areas, and tons of new "
|
||||
"and unique possibilities within a design tool."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Designers will learn CSS basics while working, "
|
||||
"and as always with Penpot, developers can pick"
|
||||
" up the design as code to take it from there."]]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Designers will learn CSS basics while working, "
|
||||
"and as always with Penpot, developers can pick"
|
||||
" up the design as code to take it from there."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
1
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-new-ui.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's UI Makeover"}]
|
||||
1
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-new-ui.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's UI Makeover"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"UI Makeover - Smoother, Sharper, and Simply More Fun"]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"UI Makeover - Smoother, Sharper, and Simply More Fun"]]
|
||||
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"We've completely overhauled Penpot's user interface. "
|
||||
"The improvements in consistency, the introduction of "
|
||||
"new microinteractions, and attention to countless details"
|
||||
" will significantly enhance the productivity and enjoyment of using Penpot."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Furthermore, we’ve made several accessibility improvements, "
|
||||
"with better color contrast, keyboard navigation,"
|
||||
" and adherence to other best practices."]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"We've completely overhauled Penpot's user interface. "
|
||||
"The improvements in consistency, the introduction of "
|
||||
"new microinteractions, and attention to countless details"
|
||||
" will significantly enhance the productivity and enjoyment of using Penpot."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Furthermore, we’ve made several accessibility improvements, "
|
||||
"with better color contrast, keyboard navigation,"
|
||||
" and adherence to other best practices."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
2
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-components.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's new components system"}]
|
||||
2
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-components.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot's new components system"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"New Components System"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"The new Penpot components system improves"
|
||||
" control over instances, including their "
|
||||
"inheritances and properties overrides. "
|
||||
"Main components are now accessible as design"
|
||||
" elements, allowing a better updating "
|
||||
"workflow through instant changes synchronization."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"And that’s not all, there are new capabilities "
|
||||
"such as component swapping and annotations "
|
||||
"that will help you to better manage your design systems."]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"New Components System"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"The new Penpot components system improves"
|
||||
" control over instances, including their "
|
||||
"inheritances and properties overrides. "
|
||||
"Main components are now accessible as design"
|
||||
" elements, allowing a better updating "
|
||||
"workflow through instant changes synchronization."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"And that’s not all, there are new capabilities "
|
||||
"such as component swapping and annotations "
|
||||
"that will help you to better manage your design systems."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
3
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-html.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt " Penpot's HTML code generator"}]
|
||||
3
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.0-html.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt " Penpot's HTML code generator"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"And much more"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"In addition to all of this, we’ve included several other requested improvements:"]
|
||||
[:ul {:class (stl/css :feature-list)}
|
||||
[:li "Access HTML markup code directly in inspect mode"]
|
||||
[:li "Images are now treated as element fills, maintaining their aspect ratio on resize, ideal for flexible designs"]
|
||||
[:li "Enjoy new color themes with options for both dark and light modes"]
|
||||
[:li "Feel the speed boost! Enjoy a smoother experience with a bunch of performance improvements"]]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"And much more"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"In addition to all of this, we’ve included several other requested improvements:"]
|
||||
[:ul {:class (stl/css :feature-list)}
|
||||
[:li "Access HTML markup code directly in inspect mode"]
|
||||
[:li "Images are now treated as element fills, maintaining their aspect ratio on resize, ideal for flexible designs"]
|
||||
[:li "Enjoy new color themes with options for both dark and light modes"]
|
||||
[:li "Feel the speed boost! Enjoy a smoother experience with a bunch of performance improvements"]]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:div {:class (stl/css :navigation)}
|
||||
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 4}]
|
||||
|
||||
[:button {:on-click finish
|
||||
:class (stl/css :next-btn)} "Let's go"]]]]]]))))
|
||||
[:button {:on-click finish
|
||||
:class (stl/css :next-btn)} "Let's go"]]]]]])))
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
[:li {:class (stl/css-case :current options?
|
||||
:settings-item true)
|
||||
:on-click go-settings-options
|
||||
:data-testid "settings-profile"
|
||||
:data-test "settings-profile"}
|
||||
[:span {:class (stl/css :element-title)} (tr "labels.settings")]]
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue