diff --git a/backend/resources/app/onboarding.edn b/backend/resources/app/onboarding.edn index 3c9b4d8d9..408835642 100644 --- a/backend/resources/app/onboarding.edn +++ b/backend/resources/app/onboarding.edn @@ -1,4 +1,8 @@ -[{:id "penpot-design-system" +[{:id "tutorial-for-beginners" + :name "Tutorial for beginners" + :thumbnail-uri "https://penpot.app/images/libraries/tutorial-for-beginners.jpg" + :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/tutorial-for-beginners.penpot"} + {:id "penpot-design-system" :name "Penpot Design System" :thumbnail-uri "https://penpot.app/images/libraries/cover-ds-penpot.jpg" :file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Penpot-Design-system.penpot"} diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index 8192e9f61..6c9afdb19 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -246,6 +246,8 @@ props (-> (audit/extract-utm-params params) (merge (:props params)) + (merge {:viewed-tutorial? false + :viewed-walkthrough? false}) (db/tjson)) password (if-let [password (:password params)] diff --git a/frontend/resources/images/hands-on-tutorial.png b/frontend/resources/images/hands-on-tutorial.png new file mode 100644 index 000000000..c3aaeda21 Binary files /dev/null and b/frontend/resources/images/hands-on-tutorial.png differ diff --git a/frontend/resources/images/walkthrough-cover.png b/frontend/resources/images/walkthrough-cover.png new file mode 100644 index 000000000..eb602f871 Binary files /dev/null and b/frontend/resources/images/walkthrough-cover.png differ diff --git a/frontend/resources/styles/main/partials/dashboard.scss b/frontend/resources/styles/main/partials/dashboard.scss index e005b27ce..a3b601646 100644 --- a/frontend/resources/styles/main/partials/dashboard.scss +++ b/frontend/resources/styles/main/partials/dashboard.scss @@ -43,13 +43,9 @@ } } .invite { - background-color: $color-primary; - color: $color-black; width: 180px; height: 40px; - border: none; align-self: flex-end; - cursor: pointer; } img { width: 274px; @@ -61,6 +57,112 @@ } } +.hero-projects { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 30px; + margin: 0 1rem 1rem 1.2rem; + .tutorial, + .walkthrough { + display: grid; + grid-template-columns: 1fr 1fr; + position: relative; + border: 2px solid $color-gray-10; + border-radius: 8px; + min-height: 211px; + + .img { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + padding: 30px; + display: block; + background-color: #e0e4e9; + } + + .text { + padding: 30px; + .title { + color: $color-black; + font-size: $fs24; + font-weight: bold; + margin-bottom: 8px; + } + .info { + color: $color-gray-30; + margin-bottom: 20px; + font-size: $fs16; + } + } + .action { + font-family: "worksans", sans-serif; + width: 180px; + height: 40px; + } + .close { + position: absolute; + top: 0; + right: 0; + width: $size-5; + cursor: pointer; + display: flex; + margin: 20px; + justify-content: center; + align-items: center; + background-color: transparent; + border: none; + .icon { + svg { + fill: $color-gray-30; + height: 16px; + width: 16px; + transform: rotate(45deg); + &:hover { + fill: $color-primary; + } + } + } + } + + @media (max-width: 1846px) { + grid-template-columns: 190px 1fr; + } + @media (max-width: 1526px) { + grid-template-columns: 1fr; + .img { + display: none; + width: 0; + } + .text { + .info { + // font-size: $fs12; + } + } + } + } + .walkthrough { + .img { + background-image: url("/images/walkthrough-cover.png"); + background-position: center; + background-repeat: no-repeat; + background-size: cover; + } + } + .tutorial { + .img { + background-image: url("/images/hands-on-tutorial.png"); + background-position: center; + background-repeat: no-repeat; + background-size: cover; + } + .loader { + display: flex; + svg#loader-pencil { + width: 31px; + } + } + } +} + .dashboard-container { background-color: $color-dashboard; flex: 1 0 0; @@ -328,8 +430,8 @@ width: 100%; text-align: right; height: 56px; - cursor: pointer; div { + cursor: pointer; height: 58px; display: inline-block; line-height: 58px; diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index c2d7b84c8..bd506bcd5 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -296,7 +296,7 @@ (rx/map recent-files-fetched))))))) -;; --- EVENT: fetch-team-invitations +;; --- EVENT: fetch-template-files (defn builtin-templates-fetched [libraries] diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index 6583d04b8..cde149b2a 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -323,7 +323,6 @@ (dcm/close-thread) (rt/nav :viewer pparams (assoc qparams :index (inc index))))))))) - (def select-first-frame (ptk/reify ::select-first-frame ptk/WatchEvent diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 3f631babd..2b9660027 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -64,7 +64,9 @@ (mf/defc templates-section [{:keys [default-project-id profile project team content-width] :as props}] - (let [templates (mf/deref builtin-templates) + (let [templates (->> (mf/deref builtin-templates) + (filter #(not= (:id %) "tutorial-for-beginners"))) + route (mf/deref refs/route) route-name (get-in route [:data :name]) section (if (= route-name :dashboard-files) @@ -141,8 +143,8 @@ :section section})))] [:div.dashboard-templates-section {:class (when collapsed "collapsed")} - [:div.title {:on-click toggle-collapse} - [:div + [:div.title + [:div {:on-click toggle-collapse} [:span (tr "dashboard.libraries-and-templates")] [:span.icon (if collapsed i/arrow-up i/arrow-down)]]] [:div.content {:ref content-ref @@ -192,7 +194,10 @@ (case section :dashboard-projects [:* - [:& projects-section {:team team :projects projects :profile profile}] + [:& projects-section {:team team + :projects projects + :profile profile + :default-project-id default-project-id}] [:& templates-section {:profile profile :project project :default-project-id default-project-id diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index d79ff1704..f2601e543 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -9,6 +9,7 @@ [app.common.math :as mth] [app.main.data.dashboard :as dd] [app.main.data.events :as ev] + [app.main.data.messages :as dm] [app.main.data.modal :as modal] [app.main.data.users :as du] [app.main.refs :as refs] @@ -51,10 +52,78 @@ [:div.info [:span (tr "dasboard.team-hero.text")] [:a {:on-click go-members} (tr "dasboard.team-hero.management")]]] - [:button.invite {:on-click invite-member} (tr "onboarding.choice.team-up.invite-members")] + [:button.btn-primary.invite {:on-click invite-member} (tr "onboarding.choice.team-up.invite-members")] [:button.close {:on-click close-banner} [:span i/close]]])) +(def builtin-templates + (l/derived :builtin-templates st/state)) + +(mf/defc tutorial-project + [{:keys [close-tutorial default-project-id] :as props}] + (let [state (mf/use-state + {:status :waiting + :file nil}) + + template (->> (mf/deref builtin-templates) + (filter #(= (:id %) "tutorial-for-beginners")) + first) + + on-template-cloned-success + (mf/use-callback + (fn [response] + (swap! state #(assoc % :status :success :file (:first response))) + (st/emit! (dd/go-to-workspace {:id (first response) :project-id default-project-id :name "tutorial"}) + (du/update-profile-props {:viewed-tutorial? true})))) + + on-template-cloned-error + (fn [] + (swap! state #(assoc % :status :waiting)) + (st/emit! + (dm/error (tr "dashboard.libraries-and-templates.import-error")))) + + download-tutorial + (fn [] + (let [mdata {:on-success on-template-cloned-success :on-error on-template-cloned-error} + params {:project-id default-project-id :template-id (:id template)}] + (swap! state #(assoc % :status :importing)) + (st/emit! (with-meta (dd/clone-template (with-meta params mdata)) + {::ev/origin "get-started-hero-block"}))))] + [:div.tutorial + [:div.img] + [:div.text + [:div.title (tr "dasboard.tutorial-hero.title")] + [:div.info (tr "dasboard.tutorial-hero.info")] + [:button.btn-primary.action {:on-click download-tutorial} + (case (:status @state) + :waiting (tr "dasboard.tutorial-hero.start") + :importing [:span.loader i/loader-pencil] + :success "" + ) + ]] + [:button.close + {:on-click close-tutorial} + [:span.icon i/close]]])) + +(mf/defc interface-walkthrough + {::mf/wrap [mf/memo]} + [{:keys [close-walkthrough] :as props}] + (let [handle-walkthrough-link + (fn [] + (st/emit! (ptk/event ::ev/event {::ev/name "show-walkthrough" + ::ev/origin "get-started-hero-block" + :section "dashboard"})))] + [:div.walkthrough + [:div.img] + [:div.text + [:div.title (tr "dasboard.walkthrough-hero.title")] + [:div.info (tr "dasboard.walkthrough-hero.info")] + [:a.btn-primary.action {:href " https://design.penpot.app/walkthrough" :target "_blank" :on-click handle-walkthrough-link} + (tr "dasboard.walkthrough-hero.start")]] + [:button.close + {:on-click close-walkthrough} + [:span.icon i/close]]])) + (mf/defc project-item [{:keys [project first? team files] :as props}] (let [locale (mf/deref i18n/locale) @@ -215,19 +284,37 @@ (l/derived :dashboard-recent-files st/state)) (mf/defc projects-section - [{:keys [team projects profile] :as props}] - (let [projects (->> (vals projects) - (sort-by :modified-at) - (reverse)) - recent-map (mf/deref recent-files-ref) - props (some-> profile (get :props {})) - team-hero? (:team-hero? props true) + [{:keys [team projects profile default-project-id] :as props}] + (let [projects (->> (vals projects) + (sort-by :modified-at) + (reverse)) + recent-map (mf/deref recent-files-ref) + props (some-> profile (get :props {})) + team-hero? (:team-hero? props true) + tutorial-viewed? (:viewed-tutorial? props true) + walkthrough-viewed? (:viewed-walkthrough? props true) - close-banner (fn [] - (st/emit! - (du/update-profile-props {:team-hero? false}) - (ptk/event ::ev/event {::ev/name "dont-show-team-up-hero" - ::ev/origin "dashboard"})))] + close-banner (fn [] + (st/emit! + (du/update-profile-props {:team-hero? false}) + (ptk/event ::ev/event {::ev/name "dont-show-team-up-hero" + ::ev/origin "dashboard"}))) + + close-tutorial (fn [] + (st/emit! + (du/update-profile-props {:viewed-tutorial? true}) + (ptk/event ::ev/event {::ev/name "dont-show" + ::ev/origin "get-started-hero-block" + :type "tutorial" + :section "dashboard"}))) + + close-walkthrough (fn [] + (st/emit! + (du/update-profile-props {:viewed-walkthrough? true}) + (ptk/event ::ev/event {::ev/name "dont-show" + ::ev/origin "get-started-hero-block" + :type "walkthrough" + :section "dashboard"})))] (mf/use-effect (mf/deps team) @@ -247,9 +334,19 @@ [:* [:& header] (when (and team-hero? (not (:is-default team))) - [:& team-hero + [:& team-hero {:team team :close-banner close-banner}]) + (when (or (not tutorial-viewed?) (not walkthrough-viewed?)) + [:div.hero-projects + (when (and (not tutorial-viewed?) (:is-default team)) + [:& tutorial-project + {:close-tutorial close-tutorial + :default-project-id default-project-id}]) + + (when (and (not walkthrough-viewed?) (:is-default team)) + [:& interface-walkthrough + {:close-walkthrough close-walkthrough}])]) [:section.dashboard-container.no-bg (for [{:keys [id] :as project} projects] diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index 9cb61dd79..1b35d882e 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -202,9 +202,11 @@ (vals) (filter cph/text-shape?))) - zoom (:zoom local) - frames (:frames page) - frame (get frames index) + zoom (:zoom local) + zoom-type (:zoom-type local) + + frames (:frames page) + frame (get frames index) fullscreen? (mf/deref refs/viewer-fullscreen?) overlays (:overlays local) @@ -262,7 +264,6 @@ (reset! scroll (dom/get-target-scroll event)))] (hooks/use-shortcuts ::viewer sc/shortcuts) - (when (nil? page) (ex/raise :type :not-found)) @@ -313,9 +314,21 @@ (wapi/request-fullscreen wrapper) (wapi/exit-fullscreen)))))) + (mf/use-layout-effect + (mf/deps page) + (fn [] + (case zoom-type + :fit (st/emit! dv/zoom-to-fit) + :fill (st/emit! dv/zoom-to-fill) + nil))) + (mf/use-layout-effect (mf/deps index) (fn [] + (case zoom-type + :fit (st/emit! dv/zoom-to-fit) + :fill (st/emit! dv/zoom-to-fill) + nil) ;; Navigate animation needs to be started after navigation ;; is complete, and we have the next page index. (when (and current-animation diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 6643345af..d05dc5495 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -476,6 +476,30 @@ msgstr "Penpot is meant for teams. Invite members to work together on projects a msgid "dasboard.team-hero.management" msgstr "Team management" +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Hands on Tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Learn the basics at Penpot while having some fun with this hands on tutorial." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Start the tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Interface Walkthrough" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Take a walk through Penpot and get to know its main features." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Start the tour" + #: src/app/main/data/dashboard.cljs msgid "dashboard.new-file-prefix" msgstr "New File" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 6f7e89567..ce5b3ecc0 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -496,6 +496,31 @@ msgstr " Penpot está dirigido a equipos. Invita a personas para trabajar conjun msgid "dasboard.team-hero.management" msgstr "Gestión del equipo" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial práctico" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Aprende los básicos de Penpot mientras pasas un buen rato con este tutorial práctico." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Comenzar tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Recorrido por el interfaz" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr " Da un paseo por Penpot para conocer sus principales funcionalidades." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Comenzar" + #: src/app/main/data/dashboard.cljs msgid "dashboard.new-project-prefix" msgstr "Nuevo Proyecto"