mirror of
https://github.com/penpot/penpot.git
synced 2026-08-29 16:18:48 +00:00
✨ Add check version menu entry
This commit is contained in:
parent
0e388442a1
commit
480de64083
329
frontend/src/app/main/ui/dashboard/check_updates.cljs
Normal file
329
frontend/src/app/main/ui/dashboard/check_updates.cljs
Normal file
@ -0,0 +1,329 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns app.main.ui.dashboard.check-updates
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.config :as cf]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
|
||||
[app.main.ui.ds.foundations.typography :as t]
|
||||
[app.main.ui.ds.foundations.typography.heading :refer [heading*]]
|
||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.http :as http]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.string :as cstr]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private telemetry-origin
|
||||
"check-updates-modal")
|
||||
|
||||
(def ^:private highlights-md-url
|
||||
"https://raw.githubusercontent.com/penpot/penpot/refs/heads/staging/HIGHLIGHTS.md")
|
||||
|
||||
(def ^:private changelog-url
|
||||
"https://github.com/penpot/penpot/blob/staging/CHANGES.md")
|
||||
|
||||
(def ^:private release-notes-url
|
||||
"https://penpot.app/release-notes")
|
||||
|
||||
(def ^:private version-heading-re
|
||||
#"(?m)^##\s+(\d+\.\d+\.\d+)(.*)$")
|
||||
|
||||
(def ^:private bullet-re
|
||||
#"^- (.+)$")
|
||||
|
||||
(defn- unreleased-suffix?
|
||||
[suffix]
|
||||
(str/includes? (str/lower (or suffix "")) "unreleased"))
|
||||
|
||||
(defn- parse-section-items
|
||||
[section]
|
||||
(->> (cstr/split-lines section)
|
||||
(keep (fn [line]
|
||||
(when-let [[_ item] (re-matches bullet-re (str/trim line))]
|
||||
item)))
|
||||
vec))
|
||||
|
||||
(def ^:private version-triple-re
|
||||
#"^(\d+)\.(\d+)\.(\d+)$")
|
||||
|
||||
(defn- version-triple
|
||||
[version]
|
||||
(when-let [[_ major minor patch] (re-matches version-triple-re version)]
|
||||
[(js/parseInt major 10)
|
||||
(js/parseInt minor 10)
|
||||
(js/parseInt patch 10)]))
|
||||
|
||||
(defn version-compare
|
||||
"Compare two X.Y.Z versions by major, minor, and patch."
|
||||
[a b]
|
||||
(let [[ma mi pa] (or (version-triple a) [0 0 0])
|
||||
[mb mj pb] (or (version-triple b) [0 0 0])]
|
||||
(or (when (not= ma mb) (- ma mb))
|
||||
(when (not= mi mj) (- mi mj))
|
||||
(- pa pb))))
|
||||
|
||||
(defn version-newer?
|
||||
[a b]
|
||||
(pos? (version-compare a b)))
|
||||
|
||||
(defn parse-highlights
|
||||
"Parse HIGHLIGHTS.md into released version sections with bullet items.
|
||||
Skips Unreleased headings. Preserves file order (newest first)."
|
||||
[markdown]
|
||||
(if-not (string? markdown)
|
||||
[]
|
||||
(->> (str/split markdown #"(?m)(?=^##\s+\d+\.\d+\.\d+)")
|
||||
(keep (fn [part]
|
||||
(when-let [[_ version suffix] (re-find version-heading-re part)]
|
||||
(when-not (unreleased-suffix? suffix)
|
||||
{:version version
|
||||
:items (parse-section-items part)}))))
|
||||
vec)))
|
||||
|
||||
(defn parse-latest-released-version
|
||||
"Return the first non-unreleased `## X.Y.Z` heading from a highlights body."
|
||||
[markdown]
|
||||
(some-> (parse-highlights markdown) first :version))
|
||||
|
||||
(defn highlights-until-installed
|
||||
"Keep released sections newer than the installed version (major, minor,
|
||||
patch). Stops at the installed version or any older section."
|
||||
[highlights installed]
|
||||
(into []
|
||||
(take-while #(version-newer? (:version %) installed))
|
||||
highlights))
|
||||
|
||||
(defn- show-available-dialog
|
||||
[{:keys [installed latest highlights]}]
|
||||
(st/emit! (modal/show {:type :check-updates-available
|
||||
:installed installed
|
||||
:latest latest
|
||||
:highlights highlights})))
|
||||
|
||||
(defn- show-uptodate-dialog
|
||||
[version]
|
||||
(st/emit! (modal/show {:type :check-updates-uptodate
|
||||
:version version})))
|
||||
|
||||
(defn- show-unable-dialog
|
||||
[]
|
||||
(st/emit! (modal/show {:type :check-updates-unable})))
|
||||
|
||||
(defn- handle-highlights
|
||||
[installed body]
|
||||
(let [sections (parse-highlights body)
|
||||
latest (some-> sections first :version)]
|
||||
(cond
|
||||
(nil? latest)
|
||||
(show-unable-dialog)
|
||||
|
||||
(not (version-newer? latest installed))
|
||||
(show-uptodate-dialog installed)
|
||||
|
||||
:else
|
||||
(show-available-dialog
|
||||
{:installed installed
|
||||
:latest latest
|
||||
:highlights (highlights-until-installed sections installed)}))))
|
||||
|
||||
(defn check-for-updates!
|
||||
([current-version]
|
||||
(check-for-updates! current-version nil))
|
||||
([current-version {:keys [on-start on-finish]}]
|
||||
(when on-start (on-start))
|
||||
(->> (http/send! {:method :get
|
||||
:mode :cors
|
||||
:omit-default-headers true
|
||||
:uri highlights-md-url
|
||||
:response-type :text})
|
||||
(rx/subs!
|
||||
(fn [response]
|
||||
(when on-finish (on-finish))
|
||||
(if (http/success? response)
|
||||
(handle-highlights current-version (:body response))
|
||||
(show-unable-dialog)))
|
||||
(fn [_cause]
|
||||
(when on-finish (on-finish))
|
||||
(show-unable-dialog))))))
|
||||
|
||||
(mf/defc check-updates-unable-modal*
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :check-updates-unable}
|
||||
[_]
|
||||
(let [on-close
|
||||
(mf/use-fn #(st/emit! (modal/hide)))
|
||||
|
||||
on-try-again
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(st/emit! (modal/hide))
|
||||
(check-for-updates! (:base cf/version))))]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:div {:class (stl/css :modal-title-row)}
|
||||
[:> icon* {:icon-id i/msg-neutral
|
||||
:class (stl/css :modal-title-icon)
|
||||
:aria-hidden true}]
|
||||
[:> heading* {:level 2
|
||||
:typography "headline-medium"
|
||||
:class (stl/css :modal-title)}
|
||||
(tr "dashboard.check-updates.unable-title")]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click on-close
|
||||
:icon i/close
|
||||
:class (stl/css :modal-close-btn)}]]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:> text* {:as "p"
|
||||
:typography t/body-large
|
||||
:class (stl/css :modal-msg)}
|
||||
(tr "dashboard.check-updates.unable-message")]
|
||||
[:> text* {:as "p"
|
||||
:typography t/body-large
|
||||
:class (stl/css :modal-msg)}
|
||||
(tr "dashboard.check-updates.unable-hint")]]
|
||||
|
||||
[:div {:class (stl/css :modal-footer)}
|
||||
[:> button* {:variant "primary"
|
||||
:on-click on-try-again}
|
||||
(tr "dashboard.check-updates.try-again")]]]]))
|
||||
|
||||
(mf/defc check-updates-uptodate-modal*
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :check-updates-uptodate}
|
||||
[{:keys [version]}]
|
||||
(let [on-close
|
||||
(mf/use-fn #(st/emit! (modal/hide)))]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:div {:class (stl/css :modal-title-row)}
|
||||
[:> icon* {:icon-id i/tick
|
||||
:class (stl/css :modal-title-icon)
|
||||
:aria-hidden true}]
|
||||
[:> heading* {:level 2
|
||||
:typography "headline-medium"
|
||||
:class (stl/css :modal-title)}
|
||||
(tr "dashboard.check-updates.uptodate-title")]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click on-close
|
||||
:icon i/close
|
||||
:class (stl/css :modal-close-btn)}]]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:> text* {:as "p"
|
||||
:typography t/body-large
|
||||
:class (stl/css :modal-msg)}
|
||||
(tr "dashboard.check-updates.uptodate-message")
|
||||
" "
|
||||
[:span {:class (stl/css :version)} version]]]
|
||||
|
||||
[:div {:class (stl/css :modal-footer)}
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click on-close}
|
||||
(tr "labels.close")]]]]))
|
||||
|
||||
(mf/defc check-updates-available-modal*
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :check-updates-available}
|
||||
[{:keys [installed latest highlights]}]
|
||||
(let [on-close
|
||||
(mf/use-fn #(st/emit! (modal/hide)))
|
||||
|
||||
on-changelog
|
||||
(mf/use-fn
|
||||
(mf/deps installed)
|
||||
(fn []
|
||||
(st/emit! (ev/event {::ev/name "explore-changelog-click"
|
||||
::ev/origin telemetry-origin
|
||||
:version installed}))
|
||||
(dom/open-new-window changelog-url)))
|
||||
|
||||
on-release-notes
|
||||
(mf/use-fn
|
||||
(mf/deps installed)
|
||||
(fn []
|
||||
(st/emit! (ev/event {::ev/name "explore-product-updates-click"
|
||||
::ev/origin telemetry-origin
|
||||
:version installed}))
|
||||
(dom/open-new-window release-notes-url)))]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-container :modal-container-available)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:div {:class (stl/css :modal-title-row)}
|
||||
[:> icon* {:icon-id i/info
|
||||
:class (stl/css :modal-title-icon)
|
||||
:aria-hidden true}]
|
||||
[:> heading* {:level 2
|
||||
:typography "headline-medium"
|
||||
:class (stl/css :modal-title)}
|
||||
(tr "dashboard.check-updates.available-title")]]
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "labels.close")
|
||||
:on-click on-close
|
||||
:icon i/close
|
||||
:class (stl/css :modal-close-btn)}]]
|
||||
|
||||
[:div {:class (stl/css :version-bar)}
|
||||
[:span {:class (stl/css :version-bar-side)}
|
||||
(tr "dashboard.check-updates.installed-version")
|
||||
" "
|
||||
[:span {:class (stl/css :version)} installed]]
|
||||
[:> icon* {:icon-id i/arrow-right
|
||||
:class (stl/css :version-bar-arrow)
|
||||
:size "s"
|
||||
:aria-hidden true}]
|
||||
[:span {:class (stl/css :version-bar-side)}
|
||||
(tr "dashboard.check-updates.latest-version")
|
||||
" "
|
||||
[:span {:class (stl/css :version :version-accent)} latest]]]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:> text* {:as "p"
|
||||
:typography t/body-large
|
||||
:class (stl/css :modal-msg)}
|
||||
(tr "dashboard.check-updates.available-message")]
|
||||
|
||||
[:> text* {:as "h3"
|
||||
:typography t/headline-small
|
||||
:class (stl/css :highlights-title)}
|
||||
(tr "dashboard.check-updates.highlights-title")]
|
||||
|
||||
[:div {:class (stl/css :highlights-scroll)}
|
||||
(for [section highlights]
|
||||
(let [version (:version section)
|
||||
items (:items section)]
|
||||
[:div {:key version
|
||||
:class (stl/css :highlights-section)}
|
||||
[:div {:class (stl/css :highlights-version)} version]
|
||||
[:ul {:class (stl/css :highlights-list)}
|
||||
(for [item items]
|
||||
[:li {:key item
|
||||
:class (stl/css :highlights-item)}
|
||||
item])]]))]]
|
||||
|
||||
[:div {:class (stl/css :modal-footer :modal-footer-available)}
|
||||
[:> button* {:variant "secondary"
|
||||
:on-click on-changelog}
|
||||
(tr "dashboard.check-updates.view-changelog")]
|
||||
[:> button* {:variant "primary"
|
||||
:on-click on-release-notes}
|
||||
(tr "dashboard.check-updates.view-release-notes")]]]]))
|
||||
175
frontend/src/app/main/ui/dashboard/check_updates.scss
Normal file
175
frontend/src/app/main/ui/dashboard/check_updates.scss
Normal file
@ -0,0 +1,175 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
@use "ds/_utils.scss" as *;
|
||||
@use "ds/_sizes.scss" as *;
|
||||
@use "ds/_borders.scss" as *;
|
||||
@use "ds/typography.scss" as *;
|
||||
|
||||
.modal-overlay {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: var(--z-index-set);
|
||||
background-color: var(--color-overlay-default);
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xxl);
|
||||
padding: var(--sp-xxxl);
|
||||
border-radius: $br-8;
|
||||
background-color: var(--color-background-primary);
|
||||
border: $b-2 solid var(--color-background-quaternary);
|
||||
min-inline-size: $sz-364;
|
||||
max-inline-size: $sz-512;
|
||||
}
|
||||
|
||||
.modal-container-available {
|
||||
max-inline-size: $sz-480;
|
||||
max-block-size: min(90vh, $sz-712);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--sp-m);
|
||||
padding-inline-end: var(--sp-xl);
|
||||
}
|
||||
|
||||
.modal-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.modal-title-icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
|
||||
.modal-close-btn {
|
||||
position: absolute;
|
||||
inset-block-start: var(--sp-m);
|
||||
inset-inline-end: var(--sp-m);
|
||||
}
|
||||
|
||||
.version-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--sp-m);
|
||||
padding: var(--sp-m) var(--sp-l);
|
||||
border-radius: $br-8;
|
||||
background-color: var(--color-background-tertiary);
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.version-bar-side {
|
||||
@include use-typography("body-large");
|
||||
}
|
||||
|
||||
.version-bar-arrow {
|
||||
flex-shrink: 0;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-s);
|
||||
min-block-size: 0;
|
||||
}
|
||||
|
||||
.modal-msg {
|
||||
margin: 0;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.version {
|
||||
color: var(--color-foreground-primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.version-accent {
|
||||
color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
.highlights-title {
|
||||
margin: var(--sp-m) 0 0;
|
||||
color: var(--color-foreground-secondary);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.highlights-scroll {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-l);
|
||||
margin-block-start: var(--sp-s);
|
||||
padding-inline-end: var(--sp-s);
|
||||
max-block-size: $sz-284;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.highlights-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.highlights-version {
|
||||
@include use-typography("headline-small");
|
||||
|
||||
color: var(--color-foreground-primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.highlights-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-xs);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.highlights-item {
|
||||
@include use-typography("body-large");
|
||||
|
||||
position: relative;
|
||||
padding-inline-start: var(--sp-l);
|
||||
color: var(--color-foreground-secondary);
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset-block-start: 0.55em;
|
||||
inset-inline-start: 0;
|
||||
inline-size: $sz-6;
|
||||
block-size: $sz-6;
|
||||
border-radius: $br-circle;
|
||||
background-color: var(--color-accent-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--sp-s);
|
||||
}
|
||||
|
||||
.modal-footer-available {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@ -26,6 +26,7 @@
|
||||
dropdown-menu-item*]]
|
||||
[app.main.ui.components.link :refer [link*]]
|
||||
[app.main.ui.components.organization-avatar :refer [organization-avatar*]]
|
||||
[app.main.ui.dashboard.check-updates :as dcu]
|
||||
[app.main.ui.dashboard.comments :refer [comments-icon* comments-section]]
|
||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
|
||||
@ -1256,8 +1257,11 @@
|
||||
|
||||
(mf/defc about-penpot-menu*
|
||||
{::mf/private true}
|
||||
[{:keys [on-close on-pointer-enter on-pointer-leave]}]
|
||||
(let [version cf/version
|
||||
[{:keys [on-close on-close-profile on-pointer-enter on-pointer-leave]}]
|
||||
(let [version cf/version
|
||||
checking* (mf/use-state false)
|
||||
checking? (deref checking*)
|
||||
|
||||
show-release-notes
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
@ -1275,7 +1279,22 @@
|
||||
(dom/get-data "eventname"))]
|
||||
(st/emit! (ev/event {::ev/name eventname
|
||||
::ev/origin "menu:in-app"}))
|
||||
(dom/open-new-window url))))]
|
||||
(dom/open-new-window url))))
|
||||
|
||||
check-for-updates
|
||||
(mf/use-fn
|
||||
(mf/deps on-close-profile version)
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(when-not @checking*
|
||||
(st/emit! (ev/event {::ev/name "check-for-updates"
|
||||
::ev/origin "menu:in-app"
|
||||
:version (:base version)}))
|
||||
(dcu/check-for-updates!
|
||||
(:base version)
|
||||
{:on-start #(reset! checking* true)
|
||||
:on-finish #(do (reset! checking* false)
|
||||
(on-close-profile))}))))]
|
||||
|
||||
[:> dropdown-menu* {:show true
|
||||
:class (stl/css :sub-menu :about)
|
||||
@ -1297,7 +1316,22 @@
|
||||
:data-url "https://penpot.app/terms"
|
||||
:on-click handle-click-url
|
||||
:data-eventname "explore-terms-service-click"}
|
||||
(tr "auth.terms-of-service")]]))
|
||||
(tr "auth.terms-of-service")]
|
||||
(when-not (contains? cf/flags :air-gapped-conf)
|
||||
[:*
|
||||
[:hr {:role "separator" :class (stl/css :submenu-separator)}]
|
||||
[:> dropdown-menu-item* {:class (stl/css-case :submenu-item true
|
||||
:checking checking?)
|
||||
:aria-disabled checking?
|
||||
:can-focus (not checking?)
|
||||
:on-click check-for-updates}
|
||||
(if checking?
|
||||
(tr "labels.checking-for-updates")
|
||||
(tr "labels.check-for-updates"))
|
||||
(when checking?
|
||||
[:> icon* {:icon-id i/reload
|
||||
:class (stl/css :checking-icon)
|
||||
:size "s"}])]])]))
|
||||
|
||||
(mf/defc profile-section*
|
||||
[{:keys [profile team]}]
|
||||
@ -1512,6 +1546,7 @@
|
||||
|
||||
:about-penpot
|
||||
[:> about-penpot-menu* {:on-close close-sub-menu
|
||||
:on-close-profile on-close
|
||||
:on-pointer-enter on-sub-menu-pointer-enter
|
||||
:on-pointer-leave on-menu-pointer-leave}]
|
||||
nil))]))
|
||||
|
||||
@ -525,6 +525,26 @@
|
||||
&:hover {
|
||||
color: var(--menu-foreground-color-hover);
|
||||
}
|
||||
|
||||
&.checking {
|
||||
color: var(--color-foreground-secondary);
|
||||
pointer-events: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-foreground-secondary);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checking-icon {
|
||||
flex-shrink: 0;
|
||||
animation: spin-animation 1s infinite linear;
|
||||
}
|
||||
|
||||
.submenu-separator {
|
||||
border-top: $b-1 solid var(--color-background-quaternary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.about-penpot {
|
||||
|
||||
@ -76,6 +76,7 @@
|
||||
[frontend-tests.tokens.token-errors-test]
|
||||
[frontend-tests.tokens.workspace-tokens-remap-test]
|
||||
[frontend-tests.ui.colorpicker-token-set-order-test]
|
||||
[frontend-tests.ui.check-updates-test]
|
||||
[frontend-tests.ui.comments-clustering-test]
|
||||
[frontend-tests.ui.comments-position-modifier-test]
|
||||
[frontend-tests.ui.ds-controls-numeric-input-test]
|
||||
|
||||
69
frontend/test/frontend_tests/ui/check_updates_test.cljs
Normal file
69
frontend/test/frontend_tests/ui/check_updates_test.cljs
Normal file
@ -0,0 +1,69 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.ui.check-updates-test
|
||||
(:require
|
||||
[app.main.ui.dashboard.check-updates :as dcu]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
|
||||
(def ^:private sample-highlights
|
||||
(str "# HIGHLIGHTS\n"
|
||||
"\n"
|
||||
"## 2.18.0 (Unreleased)\n"
|
||||
"\n"
|
||||
"- To do\n"
|
||||
"\n"
|
||||
"## 2.17.2\n"
|
||||
"\n"
|
||||
"- Background blur is here\n"
|
||||
"- WebGL rendering gets stronger\n"
|
||||
"\n"
|
||||
"## 2.17.1\n"
|
||||
"\n"
|
||||
"- MCP connection status and more\n"
|
||||
"- Design tokens: more visible, more user-friendly\n"))
|
||||
|
||||
(t/deftest parse-latest-released-version-skips-unreleased
|
||||
(t/is (= "2.17.2" (dcu/parse-latest-released-version sample-highlights))))
|
||||
|
||||
(t/deftest parse-latest-released-version-first-released
|
||||
(t/is (= "2.17.2"
|
||||
(dcu/parse-latest-released-version
|
||||
"## 2.17.2\n\n- Fix\n\n## 2.17.1\n\n- Fix\n"))))
|
||||
|
||||
(t/deftest parse-latest-released-version-only-unreleased
|
||||
(t/is (nil? (dcu/parse-latest-released-version
|
||||
"## 2.18.0 (Unreleased)\n\n- WIP\n"))))
|
||||
|
||||
(t/deftest parse-latest-released-version-empty
|
||||
(t/is (nil? (dcu/parse-latest-released-version "")))
|
||||
(t/is (nil? (dcu/parse-latest-released-version "# HIGHLIGHTS\n"))))
|
||||
|
||||
(t/deftest parse-highlights-skips-unreleased-and-collects-bullets
|
||||
(t/is (= [{:version "2.17.2"
|
||||
:items ["Background blur is here"
|
||||
"WebGL rendering gets stronger"]}
|
||||
{:version "2.17.1"
|
||||
:items ["MCP connection status and more"
|
||||
"Design tokens: more visible, more user-friendly"]}]
|
||||
(dcu/parse-highlights sample-highlights))))
|
||||
|
||||
(t/deftest version-compare
|
||||
(t/is (zero? (dcu/version-compare "2.17.1" "2.17.1")))
|
||||
(t/is (pos? (dcu/version-compare "2.17.2" "2.17.1")))
|
||||
(t/is (neg? (dcu/version-compare "2.17.1" "2.17.2")))
|
||||
(t/is (pos? (dcu/version-compare "3.0.0" "2.99.99")))
|
||||
(t/is (neg? (dcu/version-compare "2.17.2" "2.17.10"))))
|
||||
|
||||
(t/deftest highlights-until-installed
|
||||
(let [sections (dcu/parse-highlights sample-highlights)]
|
||||
(t/is (= [{:version "2.17.2"
|
||||
:items ["Background blur is here"
|
||||
"WebGL rendering gets stronger"]}]
|
||||
(dcu/highlights-until-installed sections "2.17.1")))
|
||||
(t/is (= [] (dcu/highlights-until-installed sections "2.17.2")))
|
||||
(t/is (= sections (dcu/highlights-until-installed sections "2.16.0")))
|
||||
(t/is (= [] (dcu/highlights-until-installed sections "2.17.10")))))
|
||||
@ -3680,6 +3680,51 @@ msgstr "Variant"
|
||||
msgid "labels.version-notes"
|
||||
msgstr "Version %s notes"
|
||||
|
||||
msgid "labels.check-for-updates"
|
||||
msgstr "Check for updates"
|
||||
|
||||
msgid "labels.checking-for-updates"
|
||||
msgstr "Checking for updates..."
|
||||
|
||||
msgid "dashboard.check-updates.available-title"
|
||||
msgstr "A NEW PENPOT VERSION IS AVAILABLE"
|
||||
|
||||
msgid "dashboard.check-updates.available-message"
|
||||
msgstr "Newer releases include performance improvements, new capabilities, and fixes that keep your installation current."
|
||||
|
||||
msgid "dashboard.check-updates.installed-version"
|
||||
msgstr "Installed version"
|
||||
|
||||
msgid "dashboard.check-updates.latest-version"
|
||||
msgstr "Latest version"
|
||||
|
||||
msgid "dashboard.check-updates.highlights-title"
|
||||
msgstr "What's new - Highlights"
|
||||
|
||||
msgid "dashboard.check-updates.view-changelog"
|
||||
msgstr "View full changelog"
|
||||
|
||||
msgid "dashboard.check-updates.view-release-notes"
|
||||
msgstr "View release notes"
|
||||
|
||||
msgid "dashboard.check-updates.unable-title"
|
||||
msgstr "UNABLE TO CHECK FOR UPDATES"
|
||||
|
||||
msgid "dashboard.check-updates.unable-message"
|
||||
msgstr "This installation could not reach the Penpot releases service."
|
||||
|
||||
msgid "dashboard.check-updates.unable-hint"
|
||||
msgstr "Check your network or instance configuration and try again."
|
||||
|
||||
msgid "dashboard.check-updates.try-again"
|
||||
msgstr "Try again"
|
||||
|
||||
msgid "dashboard.check-updates.uptodate-title"
|
||||
msgstr "PENPOT IS UP TO DATE"
|
||||
|
||||
msgid "dashboard.check-updates.uptodate-message"
|
||||
msgstr "You're running the latest stable version:"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/sitemap.cljs:298
|
||||
msgid "labels.view-only"
|
||||
msgstr "View only"
|
||||
|
||||
@ -3570,6 +3570,51 @@ msgstr "Variante"
|
||||
msgid "labels.version-notes"
|
||||
msgstr "Notas versión %s"
|
||||
|
||||
msgid "labels.check-for-updates"
|
||||
msgstr "Comprobar actualizaciones"
|
||||
|
||||
msgid "labels.checking-for-updates"
|
||||
msgstr "Comprobando actualizaciones..."
|
||||
|
||||
msgid "dashboard.check-updates.available-title"
|
||||
msgstr "A NEW PENPOT VERSION IS AVAILABLE"
|
||||
|
||||
msgid "dashboard.check-updates.available-message"
|
||||
msgstr "Las versiones más recientes incluyen mejoras de rendimiento, nuevas capacidades y correcciones que mantienen tu instalación al día."
|
||||
|
||||
msgid "dashboard.check-updates.installed-version"
|
||||
msgstr "Versión instalada"
|
||||
|
||||
msgid "dashboard.check-updates.latest-version"
|
||||
msgstr "Última versión"
|
||||
|
||||
msgid "dashboard.check-updates.highlights-title"
|
||||
msgstr "Novedades - Destacados"
|
||||
|
||||
msgid "dashboard.check-updates.view-changelog"
|
||||
msgstr "Ver changelog completo"
|
||||
|
||||
msgid "dashboard.check-updates.view-release-notes"
|
||||
msgstr "Ver notas de la versión"
|
||||
|
||||
msgid "dashboard.check-updates.unable-title"
|
||||
msgstr "UNABLE TO CHECK FOR UPDATES"
|
||||
|
||||
msgid "dashboard.check-updates.unable-message"
|
||||
msgstr "Esta instalación no pudo contactar con el servicio de versiones de Penpot."
|
||||
|
||||
msgid "dashboard.check-updates.unable-hint"
|
||||
msgstr "Comprueba tu red o la configuración de la instancia e inténtalo de nuevo."
|
||||
|
||||
msgid "dashboard.check-updates.try-again"
|
||||
msgstr "Intentar de nuevo"
|
||||
|
||||
msgid "dashboard.check-updates.uptodate-title"
|
||||
msgstr "PENPOT IS UP TO DATE"
|
||||
|
||||
msgid "dashboard.check-updates.uptodate-message"
|
||||
msgstr "Estás usando la última versión estable:"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/sitemap.cljs:298
|
||||
msgid "labels.view-only"
|
||||
msgstr "Solo lectura"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user