diff --git a/CHANGES.md b/CHANGES.md index 92963190c8..a216c2be0c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ## :rocket: Next ### :sparkles: New features + ### :bug: Bugs fixed ### :arrow_up: Deps updates ### :boom: Breaking changes @@ -14,21 +15,24 @@ ### :sparkles: New features -- Add many performance related improvements to indexes handling on workspace. +- Add improved workspace font selector [Taiga US #292](https://tree.taiga.io/project/penpot/us/292). - Add option to interactively scale text [Taiga #1527](https://tree.taiga.io/project/penpot/us/1527) -- Add the ability to upload/use custom fonts (and automatically generate all needed webfonts). -- Refactor dashboard state management (improves considerably the performance when you have a dashboard with a big collection of projects and files). -- Translate automatic names of new files and projects. +- Add performance improvements on dashboard data loading. +- Add performance improvements to indexes handling on workspace. +- Add the ability to upload/use custom fonts (and automatically generate all needed webfonts) [Taiga US #292](https://tree.taiga.io/project/penpot/us/292). - Transform shapes to path on double click +- Translate automatic names of new files and projects. - Use shift instead of ctrl/cmd to keep aspect ratio [Taiga 1697](https://tree.taiga.io/project/penpot/issue/1697). + ### :bug: Bugs fixed - Remove interactions when the destination artboard is deleted [Taiga #1656](https://tree.taiga.io/project/penpot/issue/1656) + ### :arrow_up: Deps updates -- Update exporter dependencies (puppetteer), that fixes some unexpected exceptions. +- Update exporter dependencies (puppeteer), that fixes some unexpected exceptions. - Update string manipulation library. @@ -38,8 +42,6 @@ configuration added scopes to the default set. Now it replaces it, so use with care, because penpot requires at least `name` and `email` props found on the user info object. -### :heart: Community contributions by (Thank you!) - ## 1.5.4-alpha diff --git a/common/app/common/data.cljc b/common/app/common/data.cljc index f1b5f10914..649e290977 100644 --- a/common/app/common/data.cljc +++ b/common/app/common/data.cljc @@ -253,14 +253,8 @@ (map (fn [x] (f x) x) coll))) (defn merge - "A faster merge." [& maps] - (loop [res (transient (or (first maps) {})) - maps (next maps)] - (if (nil? maps) - (persistent! res) - (recur (reduce-kv assoc! res (first maps)) - (next maps))))) + (reduce conj (or (first maps) {}) (rest maps))) (defn distinct-xf [f] diff --git a/frontend/dev/cljs/user.cljs b/frontend/dev/cljs/user.cljs deleted file mode 100644 index 2d251b6c78..0000000000 --- a/frontend/dev/cljs/user.cljs +++ /dev/null @@ -1 +0,0 @@ -(ns cljs.user) diff --git a/frontend/package.json b/frontend/package.json index 21a8f90694..ad653575ec 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -46,6 +46,7 @@ "randomcolor": "^0.6.2", "react": "~17.0.1", "react-dom": "~17.0.1", + "react-virtualized": "^9.22.3", "rxjs": "~7.0.1", "source-map-support": "^0.5.16", "tdigest": "^0.1.1", diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 36da777ab1..af2a9d22af 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -2,8 +2,7 @@ // 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) 2015-2016 Andrey Antukh -// Copyright (c) 2015-2016 Juan de la Cruz +// Copyright (c) UXBOX Labs SL .element-options { display: flex; @@ -809,9 +808,10 @@ left: 0; position: absolute; top: 0; - width: calc(100% - 8px); + width: calc(100%); opacity: 0.4; z-index: 10; + display: flex; } .advanced-options-wrapper { @@ -1061,37 +1061,200 @@ } .multiple-typography { - margin: 0.5rem; - padding: 0.5rem; - border: 1px dashed $color-gray-30; - border-radius: 4px; - display: flex; - justify-content: space-between; + margin: 0.5rem; + padding: 0.5rem; + border: 1px dashed $color-gray-30; + border-radius: 4px; + display: flex; + justify-content: space-between; - .multiple-typography-text, - .multiple-typography-button { - font-size: $fs13; - display: flex; - align-items: center; + .multiple-typography-text, + .multiple-typography-button { + font-size: $fs13; + display: flex; + align-items: center; + } + + .multiple-typography-button { + cursor: pointer; + svg { + transition: fill 0.3s; + width: 16px; + height: 16px; + fill: $color-gray-10; } - .multiple-typography-button { - cursor: pointer; - svg { - transition: fill 0.3s; - width: 16px; - height: 16px; - fill: $color-gray-10; - } + &:hover svg { + fill: $color-primary; + } + } - &:hover svg { - fill: $color-primary; + svg { + } + + .multiple-typography-button:hover svg { + } +} + +.font-selector { + background: $color-black; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: calc(100%); + z-index: 10; + display: flex; + justify-content: center; + align-items: center; + + .font-selector-dropdown { + background: #303236; + display: flex; + flex-direction: column; + flex-grow: 1; + height: 100%; + } + + header { + padding: 15px 17px; + display: flex; + align-items: center; + position: relative; + + .backend-filters { + padding: $small $medium; + // width: 220px; + top: 40px; + right: 20px; + } + .backend-filter { + display: flex; + align-items: center; + padding: $small 0; + cursor: pointer; + + .checkbox-icon { + display: flex; + justify-content: center; + align-items: center; + width: $medium; + height: $medium; + border: 1px solid $color-gray-30; + border-radius: $br-small; + + svg { + width: 8px; + display: none; + height: 8px; + fill: $color-black; } + } + + .backend-name { + margin-left: $small; + color: $color-gray-50; + } + + &.selected { + .checkbox-icon { + svg { + display: inherit; + } + } + } + + } + + input { + display: flex; + flex-grow: 1; + padding: 4px; + font-size: $fs12; + background: $color-gray-50; + border-radius: $br-small; + color: $color-gray-20; + border: 1px solid $color-gray-30; + margin: 0px; + } + + .options { + display: flex; + justify-content: center; + align-items: center; + width: 24px; + height: 24px; + margin-left: $small; + + svg { + width: 16px; + height: 16px; + fill: $color-gray-20 + } + + &.active { + svg { + fill: $color-primary; + } + } + } + } + + .fonts-list { + display: flex; + flex-direction: column; + height: 100%; + + position: relative; + -webkit-box-flex: 1; + flex: 1 1 auto; + } + + hr { + margin-bottom: 0px; + margin-top: 0px; + } + + .font-item { + padding-left: $big; + height: $x-big; + max-height: $x-big; + width: 100%; + display: flex; + align-items: center; + cursor: pointer; + color: $color-gray-10; + + &.selected { + background-color: $color-black; + color: $color-primary; + + .icon svg {fill: $color-primary;} + } + + &:hover { + background-color: $color-primary; + color: $color-black; + } + + .icon { + display: flex; + // justify-content: center; + align-items: center; + // border: 1px solid red; + width: $big + } + + .label { + font-size: 12px; } svg { + fill: $color-gray-10; + width: 10px; + height: 10px; } - - .multiple-typography-button:hover svg { - } + } } + + diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index 57baeec1b8..d2f630bb53 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -69,6 +69,7 @@ $width-settings-bar: 16rem; height: 100%; .tool-window { + position: relative; border-bottom: 1px solid $color-gray-60; display: flex; flex-direction: column; diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 8f48fd4984..eacc98c3ac 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -13,6 +13,7 @@ [app.main.repo :as rp] [app.main.data.events :as ev] [app.main.data.users :as du] + [app.main.data.fonts :as df] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.time :as dt] @@ -86,6 +87,7 @@ ptk/WatchEvent (watch [_ state stream] (rx/merge + (ptk/watch (df/load-team-fonts id) state stream) (ptk/watch (fetch-projects) state stream) (ptk/watch (du/fetch-teams) state stream) (ptk/watch (du/fetch-users {:team-id id}) state stream))))) diff --git a/frontend/src/app/main/data/fonts.cljs b/frontend/src/app/main/data/fonts.cljs new file mode 100644 index 0000000000..641f91044a --- /dev/null +++ b/frontend/src/app/main/data/fonts.cljs @@ -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) UXBOX Labs SL + +(ns app.main.data.fonts + (:require + [app.common.media :as cm] + [app.main.fonts :as fonts] + [app.main.repo :as rp] + [app.util.i18n :as i18n :refer [tr]] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [potok.core :as ptk])) + +(defn prepare-font-variant + [item] + {:id (str (:font-style item) "-" (:font-weight item)) + :name (str (cm/font-weight->name (:font-weight item)) " " + (str/capital (:font-style item))) + :style (:font-style item) + :weight (str (:font-weight item)) + ::fonts/woff1-file-id (:woff1-file-id item) + ::fonts/woff2-file-id (:woff2-file-id item) + ::fonts/ttf-file-id (:ttf-file-id item) + ::fonts/otf-file-id (:otf-file-id item)}) + +(defn prepare-font + [[id [item :as items]]] + {:id id + :name (:font-family item) + :family (:font-family item) + :variants (mapv prepare-font-variant items)}) + +(defn team-fonts-loaded + [fonts] + (ptk/reify ::team-fonts-loaded + ptk/EffectEvent + (effect [_ state stream] + (let [fonts (->> (group-by :font-id fonts) + (mapv prepare-font))] + (fonts/register! :custom fonts))))) + +(defn load-team-fonts + [team-id] + (ptk/reify ::load-team-fonts + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :team-font-variants {:team-id team-id}) + (rx/map team-fonts-loaded))))) + + +(defn get-fonts + [backend] + (get @fonts/fonts backend [])) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index db8f33eae5..9e034d6be4 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1258,6 +1258,15 @@ (rx/of ::dwp/force-persist (rt/nav :dashboard-projects {:team-id team-id}))))))) +(defn go-to-dashboard-fonts + [] + (ptk/reify ::go-to-dashboard + ptk/WatchEvent + (watch [it state stream] + (let [team-id (:current-team-id state)] + (rx/of ::dwp/force-persist + (rt/nav :dashboard-fonts {:team-id team-id})))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Context Menu ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 7a6d3fa592..35a6b26e02 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -15,6 +15,7 @@ [app.common.uuid :as uuid] [app.main.data.dashboard :as dd] [app.main.data.media :as di] + [app.main.data.fonts :as df] [app.main.data.messages :as dm] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] @@ -270,7 +271,8 @@ :project project :libraries libraries})) (rx/mapcat (fn [{:keys [project] :as bundle}] - (rx/of (ptk/data-event ::bundle-fetched bundle)))))))) + (rx/of (ptk/data-event ::bundle-fetched bundle) + (df/load-team-fonts (:team-id project))))))))) ;; --- Set File shared diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index 27a208b9be..8ca0ef906d 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -8,16 +8,22 @@ "Fonts management and loading logic." (:require-macros [app.main.fonts :refer [preload-gfonts]]) (:require + [app.config :as cf] [app.common.data :as d] [app.util.dom :as dom] [app.util.object :as obj] [app.util.timers :as ts] + [app.util.logging :as log] + [lambdaisland.uri :as u] + [goog.events :as gev] [beicon.core :as rx] [clojure.set :as set] [cuerdas.core :as str] [okulary.core :as l] [promesa.core :as p])) +(log/set-level! :trace) + (def google-fonts (preload-gfonts "fonts/gfonts.2020.04.23.json")) @@ -38,22 +44,27 @@ {:id "blackitalic" :name "black (italic)" :weight "900" :style "italic"}]}]) (defonce fontsdb (l/atom {})) -(defonce fontsview (l/atom {})) +(defonce fonts (l/atom [])) -(defn- materialize-fontsview - [db] - (reset! fontsview (reduce-kv (fn [acc k v] - (assoc acc k (sort-by :name v))) - {} - (group-by :backend (vals db))))) (add-watch fontsdb "main" (fn [_ _ _ db] - (ts/schedule #(materialize-fontsview db)))) + (->> (vals db) + (sort-by :name) + (map-indexed #(assoc %2 :index %1)) + (vec) + (reset! fonts)))) + +(defn- remove-fonts + [db backend] + (reduce-kv #(cond-> %1 (= backend (:backend %3)) (dissoc %2)) db db)) (defn register! [backend fonts] - (let [fonts (map #(assoc % :backend backend) fonts)] - (swap! fontsdb #(merge % (d/index-by :id fonts))))) + (swap! fontsdb + (fn [db] + (let [db (reduce-kv #(cond-> %1 (= backend (:backend %3)) (dissoc %2)) db db) + fonts (map #(assoc % :backend backend) fonts)] + (merge db (d/index-by :id fonts)))))) (register! :builtin local-fonts) (register! :google google-fonts) @@ -67,13 +78,15 @@ (defn resolve-fonts [backend] - (get @fontsview backend)) + (get @fonts backend)) -;; --- Fonts Loader +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; FONTS LOADING +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defonce loaded (l/atom #{})) -(defn- create-link-node +(defn- create-link-element [uri] (let [node (.createElement js/document "link")] (unchecked-set node "href" uri) @@ -81,32 +94,107 @@ (unchecked-set node "type" "text/css") node)) -(defn gfont-url [family variants] + +(defn- create-style-element + [css] + (let [node (.createElement js/document "style")] + (unchecked-set node "innerHTML" css) + node)) + +(defn- load-font-css! + "Creates a link element and attaches it to the dom for correctly + load external css resource." + [url on-loaded] + (let [node (create-link-element url) + head (.-head ^js js/document)] + (gev/listenOnce node "load" (fn [event] + (when (fn? on-loaded) + (on-loaded)))) + (dom/append-child! head node))) + +(defn- add-font-css! + "Creates a style element and attaches it to the dom." + [css] + (let [head (.-head ^js js/document)] + (->> (create-style-element css) + (dom/append-child! head)))) + +;; --- LOADER: BUILTIN + +(defmulti ^:private load-font :backend) + +(defmethod load-font :default + [{:keys [backend] :as font}] + (log/warn :msg "no implementation found for" :backend backend)) + +(defmethod load-font :builtin + [{:keys [id ::on-loaded] :as font}] + (log/debug :action "load-font" :font-id id :backend "builtin") + ;; (js/console.log "[debug:fonts]: loading builtin font" id) + (when (fn? on-loaded) + (on-loaded id))) + +;; --- LOADER: GOOGLE + +(defn generate-gfonts-url + [{:keys [family variants]}] (let [base (str "https://fonts.googleapis.com/css?family=" family) variants (str/join "," (map :id variants))] (str base ":" variants "&display=block"))) -(defmulti ^:private load-font :backend) - -(defmethod load-font :builtin - [{:keys [id ::on-loaded] :as font}] - (js/console.log "[debug:fonts]: loading builtin font" id) - (when (fn? on-loaded) - (on-loaded id))) - (defmethod load-font :google [{:keys [id family variants ::on-loaded] :as font}] (when (exists? js/window) - (js/console.log "[debug:fonts]: loading google font" id) - (let [node (create-link-node (gfont-url family variants))] - (.addEventListener node "load" (fn [event] (when (fn? on-loaded) - (on-loaded id)))) - (.append (.-head js/document) node) + (log/debug :action "load-font" :font-id id :backend "google") + (let [url (generate-gfonts-url font)] + (load-font-css! url (partial on-loaded id)) nil))) -(defmethod load-font :default - [{:keys [backend] :as font}] - (js/console.warn "no implementation found for" backend)) +;; --- LOADER: CUSTOM + +(def font-css-template + "@font-face { + font-family: '%(family)s'; + font-style: %(style)s; + font-weight: %(weight)s; + font-display: block; + src: url(%(woff2-uri)s) format('woff2'), + url(%(woff1-uri)s) format('woff'), + url(%(ttf-uri)s) format('ttf'), + url(%(otf-uri)s) format('otf'); + }") + +(defn- font-id->uri + [font-id] + (str (u/join cf/public-uri "assets/by-id/" font-id))) + +(defn generate-custom-font-variant-css + [family variant] + (str/fmt font-css-template + {:family family + :style (:style variant) + :weight (:weight variant) + :woff2-uri (font-id->uri (::woff2-file-id variant)) + :woff1-uri (font-id->uri (::woff1-file-id variant)) + :ttf-uri (font-id->uri (::ttf-file-id variant)) + :otf-uri (font-id->uri (::otf-file-id variant))})) + +(defn- generate-custom-font-css + [{:keys [family variants] :as font}] + (->> variants + (map #(generate-custom-font-variant-css family %)) + (str/join "\n"))) + +(defmethod load-font :custom + [{:keys [id family variants ::on-loaded] :as font}] + (when (exists? js/window) + (js/console.log "[debug:fonts]: loading google font" id) + (let [css (generate-custom-font-css font)] + (add-font-css! css)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; LOAD API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn ensure-loaded! ([id] @@ -119,7 +207,8 @@ (load-font (assoc font ::on-loaded on-loaded)) (swap! loaded conj id))))) -(defn ready [cb] +(defn ready + [cb] (-> (obj/get-in js/document ["fonts" "ready"]) (p/then cb))) diff --git a/frontend/src/app/main/ui/components/dropdown.cljs b/frontend/src/app/main/ui/components/dropdown.cljs index 82738e985f..54e33925fc 100644 --- a/frontend/src/app/main/ui/components/dropdown.cljs +++ b/frontend/src/app/main/ui/components/dropdown.cljs @@ -13,7 +13,7 @@ [props] (let [children (gobj/get props "children") on-close (gobj/get props "on-close") - ref (gobj/get props "container") + ref (gobj/get props "container") on-click (fn [event] diff --git a/frontend/src/app/main/ui/shapes/text/embed.cljs b/frontend/src/app/main/ui/shapes/text/embed.cljs index 8c5fc62848..61ef3fd4ea 100644 --- a/frontend/src/app/main/ui/shapes/text/embed.cljs +++ b/frontend/src/app/main/ui/shapes/text/embed.cljs @@ -62,7 +62,7 @@ "Given a font and the variant-id, retrieves the style CSS for it." [{:keys [id backend family variants] :as font} font-variant-id] (if (= :google backend) - (let [uri (fonts/gfont-url family [{:id font-variant-id}])] + (let [uri (fonts/generate-gfonts-url {:family family :variants [{:id font-variant-id}]})] (->> (http/send! {:method :get :mode :cors :omit-default-headers true diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs index 5c83e42cc1..2b46a3bf52 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs @@ -128,7 +128,6 @@ :on-click #(handle-change % "rtl")} i/text-direction-rtl]])) - (mf/defc vertical-align [{:keys [shapes ids values on-change] :as props}] (let [{:keys [vertical-align]} values @@ -225,63 +224,73 @@ (tr "workspace.options.text-options.title")) emit-update! - (fn [id attrs] - (let [attrs (select-keys attrs root-attrs)] - (when-not (empty? attrs) - (st/emit! (dwt/update-root-attrs {:id id :attrs attrs})))) + (mf/use-callback + (fn [id attrs] + (let [attrs (select-keys attrs root-attrs)] + (when-not (empty? attrs) + (st/emit! (dwt/update-root-attrs {:id id :attrs attrs})))) - (let [attrs (select-keys attrs paragraph-attrs)] - (when-not (empty? attrs) - (st/emit! (dwt/update-paragraph-attrs {:id id :attrs attrs})))) + (let [attrs (select-keys attrs paragraph-attrs)] + (when-not (empty? attrs) + (st/emit! (dwt/update-paragraph-attrs {:id id :attrs attrs})))) - (let [attrs (select-keys attrs text-attrs)] - (when-not (empty? attrs) - (st/emit! (dwt/update-text-attrs {:id id :attrs attrs}))))) + (let [attrs (select-keys attrs text-attrs)] + (when-not (empty? attrs) + (st/emit! (dwt/update-text-attrs {:id id :attrs attrs})))))) + + on-change + (mf/use-callback + (mf/deps ids) + (fn [attrs] + (run! #(emit-update! % attrs) ids))) typography - (cond - (and (:typography-ref-id values) - (not= (:typography-ref-id values) :multiple) - (not= (:typography-ref-file values) file-id)) - (-> shared-libs - (get-in [(:typography-ref-file values) :data :typographies (:typography-ref-id values)]) - (assoc :file-id (:typography-ref-file values))) + (mf/use-memo + (mf/deps values file-id shared-libs) + (fn [] + (cond + (and (:typography-ref-id values) + (not= (:typography-ref-id values) :multiple) + (not= (:typography-ref-file values) file-id)) + (-> shared-libs + (get-in [(:typography-ref-file values) :data :typographies (:typography-ref-id values)]) + (assoc :file-id (:typography-ref-file values))) - (and (:typography-ref-id values) - (not= (:typography-ref-id values) :multiple) - (= (:typography-ref-file values) file-id)) - (get typographies (:typography-ref-id values))) + (and (:typography-ref-id values) + (not= (:typography-ref-id values) :multiple) + (= (:typography-ref-file values) file-id)) + (get typographies (:typography-ref-id values))))) on-convert-to-typography - (mf/use-callback - (mf/deps values) - (fn [event] - (let [setted-values (-> (d/without-nils values) - (select-keys - (d/concat text-font-attrs - text-spacing-attrs - text-transform-attrs))) - typography (merge txt/default-typography setted-values) - typography (generate-typography-name typography)] - (let [id (uuid/next)] - (st/emit! (dwl/add-typography (assoc typography :id id) false)) - (run! #(emit-update! % {:typography-ref-id id - :typography-ref-file file-id}) ids))))) + (fn [event] + (let [setted-values (-> (d/without-nils values) + (select-keys + (d/concat text-font-attrs + text-spacing-attrs + text-transform-attrs))) + typography (merge txt/default-typography setted-values) + typography (generate-typography-name typography)] + (let [id (uuid/next)] + (st/emit! (dwl/add-typography (assoc typography :id id) false)) + (run! #(emit-update! % {:typography-ref-id id + :typography-ref-file file-id}) ids)))) handle-detach-typography - (fn [] - (run! #(emit-update! % {:typography-ref-file nil - :typography-ref-id nil}) - ids)) + (mf/use-callback + (mf/deps on-change) + (fn [] + (on-change {:typography-ref-file nil + :typography-ref-id nil}))) handle-change-typography - (fn [changes] - (st/emit! (dwl/update-typography (merge typography changes) file-id))) + (mf/use-callback + (mf/deps typography file-id) + (fn [changes] + (st/emit! (dwl/update-typography (merge typography changes) file-id)))) opts #js {:ids ids :values values - :on-change (fn [attrs] - (run! #(emit-update! % attrs) ids))}] + :on-change on-change}] [:div.element-set [:div.element-set-title diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index a34d102fe1..5329aa4949 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -6,20 +6,30 @@ (ns app.main.ui.workspace.sidebar.options.menus.typography (:require + ["react-virtualized" :as rvt] + [app.common.exceptions :as ex] [app.common.data :as d] [app.common.pages :as cp] [app.common.text :as txt] [app.main.data.workspace.texts :as dwt] + [app.main.data.shortcuts :as dsc] + [app.main.data.fonts :as df] + [app.main.data.workspace :as dw] [app.main.fonts :as fonts] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.editable-select :refer [editable-select]] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t]] + [app.util.object :as obj] + [app.util.timers :as tm] + [app.util.keyboard :as kbd] + [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.timers :as ts] + [goog.events :as events] [cuerdas.core :as str] [rumext.alpha :as mf])) @@ -28,74 +38,279 @@ "" (str value))) -(mf/defc font-select-optgroups +(defn- get-next-font + [{:keys [id] :as current} fonts] + (if (seq fonts) + (let [index (d/index-of-pred fonts #(= (:id %) id)) + index (or index -1) + next (ex/ignoring (nth fonts (inc index)))] + (or next (first fonts))) + current)) + +(defn- get-prev-font + [{:keys [id] :as current} fonts] + (if (seq fonts) + (let [index (d/index-of-pred fonts #(= (:id %) id)) + next (ex/ignoring (nth fonts (dec index)))] + (or next (peek fonts))) + current)) + +(mf/defc font-item {::mf/wrap [mf/memo]} - [{:keys [locale] :as props}] - [:* - [:optgroup {:label (t locale "workspace.options.text-options.preset")} - (for [font fonts/local-fonts] - [:option {:value (:id font) - :key (:id font)} - (:name font)])] - [:optgroup {:label (t locale "workspace.options.text-options.google")} - (for [font (fonts/resolve-fonts :google)] - [:option {:value (:id font) - :key (:id font)} - (:name font)])]]) + [{:keys [font current? on-click style]}] + (let [item-ref (mf/use-ref) + on-click (mf/use-callback (mf/deps font) #(on-click font))] + + (mf/use-effect + (mf/deps current?) + (fn [] + (when current? + (let [element (mf/ref-val item-ref)] + (when-not (dom/is-in-viewport? element) + (dom/scroll-into-view! element)))))) + + [:div.font-item {:ref item-ref + :style style + :class (when current? "selected") + :on-click on-click} + [:span.icon (when current? i/tick)] + [:span.label (:name font)]])) + +(declare row-renderer) + +(defn filter-fonts + [{:keys [term backends]} fonts] + (let [xform (cond-> (map identity) + (seq term) + (comp (filter #(str/includes? (str/lower (:name %)) term))) + + (seq backends) + (comp (filter #(contains? backends (:backend %)))))] + (into [] xform fonts))) + +(defn- toggle-backend + [backends id] + (if (contains? backends id) + (disj backends id) + (conj backends id))) + +(mf/defc font-selector + [{:keys [on-select on-close current-font] :as props}] + (let [selected (mf/use-state current-font) + state (mf/use-state {:term "" :backends #{}}) + + flist (mf/use-ref) + input (mf/use-ref) + ddown (mf/use-ref) + + fonts (mf/use-memo (mf/deps @state) #(filter-fonts @state @fonts/fonts)) + + select-next + (mf/use-callback + (mf/deps fonts) + (fn [event] + (dom/stop-propagation event) + (dom/prevent-default event) + (swap! selected get-next-font fonts))) + + select-prev + (mf/use-callback + (mf/deps fonts) + (fn [event] + (dom/stop-propagation event) + (dom/prevent-default event) + (swap! selected get-prev-font fonts))) + + on-key-down + (mf/use-callback + (mf/deps fonts) + (fn [event] + (cond + (kbd/up-arrow? event) (select-prev event) + (kbd/down-arrow? event) (select-next event) + (kbd/esc? event) (on-close) + (kbd/enter? event) (on-close) + :else (dom/focus! (mf/ref-val input))))) + + on-filter-change + (mf/use-callback + (mf/deps) + (fn [event] + (let [value (dom/get-target-val event)] + (swap! state assoc :term value)))) + + on-select-and-close + (mf/use-callback + (mf/deps on-select on-close) + (fn [font] + (on-select font) + (on-close))) + ] + + (mf/use-effect + (mf/deps fonts) + (fn [] + (let [key (events/listen js/document "keydown" on-key-down)] + #(events/unlistenByKey key)))) + + (mf/use-effect + (mf/deps @selected) + (fn [] + (when-let [inst (mf/ref-val flist)] + (when-let [index (:index @selected)] + (.scrollToRow ^js inst index))))) + + (mf/use-effect + (mf/deps @selected) + (fn [] + (on-select @selected))) + + (mf/use-effect + (fn [] + (st/emit! (dsc/push-shortcuts :typography {})) + (fn [] + (st/emit! (dsc/pop-shortcuts :typography))))) + + (mf/use-effect + (fn [] + (let [index (d/index-of-pred fonts #(= (:id %) (:id current-font))) + inst (mf/ref-val flist)] + (tm/schedule + #(let [offset (.getOffsetForRow ^js inst #js {:alignment "center" :index index})] + (.scrollToPosition ^js inst offset)))))) + + [:div.font-selector + [:div.font-selector-dropdown + [:header + [:input {:placeholder "Search font" + :value (:term @state) + :ref input + :spell-check false + :on-change on-filter-change}] + + #_[:div.options + {:on-click #(swap! state assoc :show-options true) + :class (when (seq (:backends @state)) "active")} + i/picker-hsv] + + #_[:& dropdown {:show (:show-options @state false) + :on-close #(swap! state dissoc :show-options)} + (let [backends (:backends @state)] + [:div.backend-filters.dropdown {:ref ddown} + [:div.backend-filter + {:class (when (backends :custom) "selected") + :on-click #(swap! state update :backends toggle-backend :custom)} + [:div.checkbox-icon i/tick] + [:div.backend-name (tr "labels.custom-fonts")]] + [:div.backend-filter + {:class (when (backends :google) "selected") + :on-click #(swap! state update :backends toggle-backend :google)} + [:div.checkbox-icon i/tick] + [:div.backend-name "Google Fonts"]]])]] + + [:hr] + + [:div.fonts-list + [:> rvt/AutoSizer {} + (fn [props] + (let [width (obj/get props "width") + height (obj/get props "height") + render #(row-renderer fonts @selected on-select-and-close %)] + (mf/html + [:> rvt/List #js {:height height + :ref flist + :width width + :rowCount (count fonts) + :rowHeight 32 + :rowRenderer render}])))]]]])) +(defn row-renderer + [fonts selected on-select props] + (let [index (obj/get props "index") + key (obj/get props "key") + style (obj/get props "style") + font (nth fonts index)] + (mf/html + [:& font-item {:key key + :font font + :style style + :on-click on-select + :current? (= (:id font) (:id selected))}]))) (mf/defc font-options - [{:keys [editor ids values locale on-change] :as props}] - (let [{:keys [font-id - font-size - font-variant-id]} values + [{:keys [editor ids values on-change] :as props}] + (let [{:keys [font-id font-size font-variant-id]} values - font-id (or font-id (:font-id txt/default-text-attrs)) - font-size (or font-size (:font-size txt/default-text-attrs)) + font-id (or font-id (:font-id txt/default-text-attrs)) + font-size (or font-size (:font-size txt/default-text-attrs)) font-variant-id (or font-variant-id (:font-variant-id txt/default-text-attrs)) - fonts (mf/deref fonts/fontsdb) - font (get fonts font-id) + fonts (mf/deref fonts/fontsdb) + font (get fonts font-id) + + open-selector? (mf/use-state false) change-font - (fn [new-font-id] - (let [{:keys [family] :as font} (get fonts new-font-id) - {:keys [id name weight style]} (fonts/get-default-variant font)] - (on-change {:font-id new-font-id - :font-family family - :font-variant-id (or id name) - :font-weight weight - :font-style style}))) + (mf/use-callback + (mf/deps on-change fonts) + (fn [new-font-id] + (let [{:keys [family] :as font} (get fonts new-font-id) + {:keys [id name weight style]} (fonts/get-default-variant font)] + (on-change {:font-id new-font-id + :font-family family + :font-variant-id (or id name) + :font-weight weight + :font-style style})))) on-font-family-change - (fn [event] - (let [new-font-id (dom/get-target-val event)] - (when-not (str/empty? new-font-id) - (let [font (get fonts new-font-id)] - (fonts/ensure-loaded! new-font-id (partial change-font new-font-id)))))) + (mf/use-callback + (mf/deps fonts change-font) + (fn [event] + (let [new-font-id (dom/get-target-val event)] + (when-not (str/empty? new-font-id) + (let [font (get fonts new-font-id)] + (fonts/ensure-loaded! new-font-id (partial change-font new-font-id))))))) on-font-size-change - (fn [new-font-size] - (when-not (str/empty? new-font-size) - (on-change {:font-size (str new-font-size)}))) + (mf/use-callback + (mf/deps on-change) + (fn [new-font-size] + (when-not (str/empty? new-font-size) + (on-change {:font-size (str new-font-size)})))) on-font-variant-change - (fn [event] - (let [new-variant-id (dom/get-target-val event) - variant (d/seek #(= new-variant-id (:id %)) (:variants font))] - (on-change {:font-id (:id font) - :font-family (:family font) - :font-variant-id new-variant-id - :font-weight (:weight variant) - :font-style (:style variant)})))] + (mf/use-callback + (mf/deps font on-change) + (fn [event] + (let [new-variant-id (dom/get-target-val event) + variant (d/seek #(= new-variant-id (:id %)) (:variants font))] + (on-change {:font-id (:id font) + :font-family (:family font) + :font-variant-id new-variant-id + :font-weight (:weight variant) + :font-style (:style variant)})))) + + on-font-select + (mf/use-callback + (mf/deps change-font) + (fn [font*] + (when (not= font font*) + (change-font (:id font*))))) + + on-font-selector-close + (mf/use-callback + #(reset! open-selector? false))] [:* + (when @open-selector? + [:& font-selector + {:current-font font + :on-close on-font-selector-close + :on-select on-font-select}]) + [:div.row-flex - [:select.input-select.font-option - {:value (attr->string font-id) - :on-change on-font-family-change} - (when (= font-id :multiple) - [:option {:value ""} (t locale "settings.multiple")]) - [:& font-select-optgroups {:locale locale}]]] + [:div.input-select.font-option + {:on-click #(reset! open-selector? true)} + (:name font)]] [:div.row-flex (let [size-options [8 9 10 11 12 14 18 24 36 48 72] @@ -121,7 +336,7 @@ (mf/defc spacing-options - [{:keys [editor ids values locale on-change] :as props}] + [{:keys [editor ids values on-change] :as props}] (let [{:keys [line-height letter-spacing]} values @@ -136,7 +351,7 @@ [:div.spacing-options [:div.input-icon [:span.icon-before.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.line-height")} + {:alt (tr "workspace.options.text-options.line-height")} i/line-height] [:input.input-text {:type "number" @@ -144,12 +359,12 @@ :min "0" :max "200" :value (attr->string line-height) - :placeholder (t locale "settings.multiple") + :placeholder (tr "settings.multiple") :on-change #(handle-change % :line-height)}]] [:div.input-icon [:span.icon-before.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.letter-spacing")} + {:alt (tr "workspace.options.text-options.letter-spacing")} i/letter-spacing] [:input.input-text {:type "number" @@ -157,11 +372,11 @@ :min "0" :max "200" :value (attr->string letter-spacing) - :placeholder (t locale "settings.multiple") + :placeholder (tr "settings.multiple") :on-change #(handle-change % :letter-spacing)}]]])) (mf/defc text-transform-options - [{:keys [editor ids values locale on-change] :as props}] + [{:keys [editor ids values on-change] :as props}] (let [{:keys [text-transform]} values text-transform (or text-transform "none") @@ -171,35 +386,32 @@ (on-change {:text-transform type}))] [:div.align-icons [:span.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.none") + {:alt (tr "workspace.options.text-options.none") :class (dom/classnames :current (= "none" text-transform)) :on-click #(handle-change % "none")} i/minus] [:span.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.uppercase") + {:alt (tr "workspace.options.text-options.uppercase") :class (dom/classnames :current (= "uppercase" text-transform)) :on-click #(handle-change % "uppercase")} i/uppercase] [:span.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.lowercase") + {:alt (tr "workspace.options.text-options.lowercase") :class (dom/classnames :current (= "lowercase" text-transform)) :on-click #(handle-change % "lowercase")} i/lowercase] [:span.tooltip.tooltip-bottom - {:alt (t locale "workspace.options.text-options.titlecase") + {:alt (tr "workspace.options.text-options.titlecase") :class (dom/classnames :current (= "capitalize" text-transform)) :on-click #(handle-change % "capitalize")} i/titlecase]])) (mf/defc typography-options [{:keys [ids editor values on-change]}] - (let [locale (mf/deref i18n/locale) - opts #js {:editor editor + (let [opts #js {:editor editor :ids ids :values values - :locale locale :on-change on-change}] - [:div.element-set-content [:> font-options opts] [:div.row-flex @@ -209,8 +421,7 @@ (mf/defc typography-entry [{:keys [typography read-only? selected? on-click on-change on-detach on-context-menu editting? focus-name? file]}] - (let [locale (mf/deref i18n/locale) - open? (mf/use-state editting?) + (let [open? (mf/use-state editting?) hover-detach (mf/use-state false) name-input-ref (mf/use-ref nil) value (mf/use-state (cp/merge-path-item (:path typography) (:name typography))) @@ -255,7 +466,7 @@ {:style {:font-family (:font-family typography) :font-weight (:font-weight typography) :font-style (:font-style typography)}} - (t locale "workspace.assets.typography.sample")] + (tr "workspace.assets.typography.sample")] [:div.typography-name (:name typography)]] [:div.element-set-actions (when on-detach @@ -277,32 +488,32 @@ [:span (:name typography)]] [:div.row-flex - [:span.label (t locale "workspace.assets.typography.font-id")] + [:span.label (tr "workspace.assets.typography.font-id")] [:span (:font-id typography)]] [:div.row-flex - [:span.label (t locale "workspace.assets.typography.font-variant-id")] + [:span.label (tr "workspace.assets.typography.font-variant-id")] [:span (:font-variant-id typography)]] [:div.row-flex - [:span.label (t locale "workspace.assets.typography.font-size")] + [:span.label (tr "workspace.assets.typography.font-size")] [:span (:font-size typography)]] [:div.row-flex - [:span.label (t locale "workspace.assets.typography.line-height")] + [:span.label (tr "workspace.assets.typography.line-height")] [:span (:line-height typography)]] [:div.row-flex - [:span.label (t locale "workspace.assets.typography.letter-spacing")] + [:span.label (tr "workspace.assets.typography.letter-spacing")] [:span (:letter-spacing typography)]] [:div.row-flex - [:span.label (t locale "workspace.assets.typography.text-transform")] + [:span.label (tr "workspace.assets.typography.text-transform")] [:span (:text-transform typography)]] [:div.go-to-lib-button {:on-click handle-go-to-edit} - (t locale "workspace.assets.typography.go-to-edit")]] + (tr "workspace.assets.typography.go-to-edit")]] [:* [:div.element-set-content diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 01bfef047f..bbda478b0c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -190,7 +190,6 @@ (defn setup-shortcuts [path-editing? drawing-path?] (hooks/use-shortcuts ::workspace wsc/shortcuts) - (mf/use-effect (mf/deps path-editing? drawing-path?) (fn [] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 6ec5ec1eec..9f786da1c9 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -46,6 +46,9 @@ msgstr "Style" msgid "labels.custom-fonts" msgstr "Custom fonts" +msgid "labels.manage-fonts" +msgstr "Manage fonts" + msgid "labels.search-font" msgstr "Search font" @@ -55,13 +58,14 @@ msgstr "Font providers" msgid "labels.upload-custom-fonts" msgstr "Upload custom fonts" + #, markdown msgid "dashboard.fonts.hero-text1" msgstr "Any web font you upload here will be added to the font family list available at the text properties of the files of this team. Fonts with the same font family name will be grouped as a **single font family**. You can upload fonts with the following formats: **TTF, OTF and WOFF** (only one will be needed)." #, markdown msgid "dashboard.fonts.hero-text2" -msgstr "You should only upload fonts you own or have license to use in Penpot. Find out more in the Content rights section of [Penpot's Terms of Service](https://penpot.app/terms.html). You also might want to read about [font licensing](2)." +msgstr "You should only upload fonts you own or have license to use in Penpot. Find out more in the Content rights section of [Penpot's Terms of Service](https://penpot.app/terms.html). You also might want to read about [font licensing](https://www.typography.com/faq)." #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 197837509d..65489d8ae0 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -47,6 +47,9 @@ msgstr "Estilo" msgid "labels.custom-fonts" msgstr "Fuentes personalizadas" +msgid "labels.manage-fonts" +msgstr "Administrar fuentes" + msgid "labels.search-font" msgstr "Buscar fuente" diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9ab017eaed..7e17a50ee1 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -10,6 +10,13 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" +"@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" + integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== + dependencies: + regenerator-runtime "^0.13.4" + "@dabh/diagnostics@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31" @@ -802,6 +809,11 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" +clsx@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" + integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== + coa@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" @@ -1147,6 +1159,11 @@ cssom@^0.3.4: resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== +csstype@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" + integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -1302,6 +1319,14 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dom-helpers@^5.1.3: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -3049,7 +3074,7 @@ logform@^2.2.0: ms "^2.1.1" triple-beam "^1.3.0" -loose-envify@^1.0.0, loose-envify@^1.1.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -4003,6 +4028,15 @@ promise@^7.1.1: dependencies: asap "~2.0.3" +prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -4119,6 +4153,28 @@ react-dom@~17.0.1: object-assign "^4.1.1" scheduler "^0.20.2" +react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-virtualized@^9.22.3: + version "9.22.3" + resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421" + integrity sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw== + dependencies: + "@babel/runtime" "^7.7.2" + clsx "^1.0.4" + dom-helpers "^5.1.3" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.4" + react@~17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"