mirror of
https://github.com/enso-org/enso.git
synced 2024-11-25 21:25:20 +03:00
Check version (#10646)
- Close https://github.com/enso-org/cloud-v2/issues/1299 - Add component that checks whether the current version of the desktop app is out of date - Add Devtools toggle so that the functionality is testable in dev servers # Important Notes - This functionality is disabled when it is not applicable: - On the Electron watch mode (as development branches do not need to be the latest version) - Note however that built apps (`./run ide build`) do still have the check enabled. - On the cloud dashboard without Electron (as it cannot be updated)
This commit is contained in:
parent
3536a18efd
commit
7a26334519
@ -49,6 +49,7 @@ import * as backendHooks from '#/hooks/backendHooks'
|
|||||||
|
|
||||||
import AuthProvider, * as authProvider from '#/providers/AuthProvider'
|
import AuthProvider, * as authProvider from '#/providers/AuthProvider'
|
||||||
import BackendProvider from '#/providers/BackendProvider'
|
import BackendProvider from '#/providers/BackendProvider'
|
||||||
|
import DevtoolsProvider from '#/providers/EnsoDevtoolsProvider'
|
||||||
import * as httpClientProvider from '#/providers/HttpClientProvider'
|
import * as httpClientProvider from '#/providers/HttpClientProvider'
|
||||||
import InputBindingsProvider from '#/providers/InputBindingsProvider'
|
import InputBindingsProvider from '#/providers/InputBindingsProvider'
|
||||||
import LocalStorageProvider, * as localStorageProvider from '#/providers/LocalStorageProvider'
|
import LocalStorageProvider, * as localStorageProvider from '#/providers/LocalStorageProvider'
|
||||||
@ -72,6 +73,7 @@ import * as subscribeSuccess from '#/pages/subscribe/SubscribeSuccess'
|
|||||||
|
|
||||||
import type * as editor from '#/layouts/Editor'
|
import type * as editor from '#/layouts/Editor'
|
||||||
import * as openAppWatcher from '#/layouts/OpenAppWatcher'
|
import * as openAppWatcher from '#/layouts/OpenAppWatcher'
|
||||||
|
import VersionChecker from '#/layouts/VersionChecker'
|
||||||
|
|
||||||
import * as devtools from '#/components/Devtools'
|
import * as devtools from '#/components/Devtools'
|
||||||
import * as errorBoundary from '#/components/ErrorBoundary'
|
import * as errorBoundary from '#/components/ErrorBoundary'
|
||||||
@ -508,7 +510,12 @@ function AppRouter(props: AppRouterProps) {
|
|||||||
</router.Routes>
|
</router.Routes>
|
||||||
)
|
)
|
||||||
|
|
||||||
let result = routes
|
let result = (
|
||||||
|
<>
|
||||||
|
<VersionChecker />
|
||||||
|
{routes}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
result = <errorBoundary.ErrorBoundary>{result}</errorBoundary.ErrorBoundary>
|
result = <errorBoundary.ErrorBoundary>{result}</errorBoundary.ErrorBoundary>
|
||||||
result = <InputBindingsProvider inputBindings={inputBindings}>{result}</InputBindingsProvider>
|
result = <InputBindingsProvider inputBindings={inputBindings}>{result}</InputBindingsProvider>
|
||||||
@ -555,8 +562,8 @@ function AppRouter(props: AppRouterProps) {
|
|||||||
{result}
|
{result}
|
||||||
</httpClientProvider.HttpClientProvider>
|
</httpClientProvider.HttpClientProvider>
|
||||||
)
|
)
|
||||||
|
|
||||||
result = <LoggerProvider logger={logger}>{result}</LoggerProvider>
|
result = <LoggerProvider logger={logger}>{result}</LoggerProvider>
|
||||||
|
result = <DevtoolsProvider>{result}</DevtoolsProvider>
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,17 @@ import * as React from 'react'
|
|||||||
|
|
||||||
import * as reactQuery from '@tanstack/react-query'
|
import * as reactQuery from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import { IS_DEV_MODE } from 'enso-common/src/detect'
|
||||||
|
|
||||||
import DevtoolsLogo from '#/assets/enso_logo.svg'
|
import DevtoolsLogo from '#/assets/enso_logo.svg'
|
||||||
|
|
||||||
import * as billing from '#/hooks/billing'
|
import * as billing from '#/hooks/billing'
|
||||||
|
|
||||||
import * as authProvider from '#/providers/AuthProvider'
|
import * as authProvider from '#/providers/AuthProvider'
|
||||||
|
import {
|
||||||
|
useEnableVersionChecker,
|
||||||
|
useSetEnableVersionChecker,
|
||||||
|
} from '#/providers/EnsoDevtoolsProvider'
|
||||||
import * as textProvider from '#/providers/TextProvider'
|
import * as textProvider from '#/providers/TextProvider'
|
||||||
|
|
||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
@ -54,6 +60,8 @@ export function EnsoDevtools(props: EnsoDevtoolsProps) {
|
|||||||
const { getText } = textProvider.useText()
|
const { getText } = textProvider.useText()
|
||||||
const { authQueryKey } = authProvider.useAuth()
|
const { authQueryKey } = authProvider.useAuth()
|
||||||
const session = authProvider.useFullUserSession()
|
const session = authProvider.useFullUserSession()
|
||||||
|
const enableVersionChecker = useEnableVersionChecker()
|
||||||
|
const setEnableVersionChecker = useSetEnableVersionChecker()
|
||||||
|
|
||||||
const [features, setFeatures] = React.useState<
|
const [features, setFeatures] = React.useState<
|
||||||
Record<billing.PaywallFeatureName, PaywallDevtoolsFeatureConfiguration>
|
Record<billing.PaywallFeatureName, PaywallDevtoolsFeatureConfiguration>
|
||||||
@ -146,6 +154,31 @@ export function EnsoDevtools(props: EnsoDevtoolsProps) {
|
|||||||
|
|
||||||
<ariaComponents.Separator orientation="horizontal" className="my-3" />
|
<ariaComponents.Separator orientation="horizontal" className="my-3" />
|
||||||
|
|
||||||
|
<ariaComponents.Text variant="subtitle" className="mb-2">
|
||||||
|
{getText('productionOnlyFeatures')}
|
||||||
|
</ariaComponents.Text>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<aria.Switch
|
||||||
|
className="group flex items-center gap-1"
|
||||||
|
isSelected={enableVersionChecker ?? !IS_DEV_MODE}
|
||||||
|
onChange={setEnableVersionChecker}
|
||||||
|
>
|
||||||
|
<div className="box-border flex h-4 w-[28px] shrink-0 cursor-default items-center rounded-full bg-primary/30 bg-clip-padding p-0.5 shadow-inner outline-none ring-black transition duration-200 ease-in-out group-focus-visible:ring-2 group-pressed:bg-primary/60 group-selected:bg-primary group-selected:group-pressed:bg-primary/50">
|
||||||
|
<span className="aspect-square h-full flex-none translate-x-0 transform rounded-full bg-white transition duration-200 ease-in-out group-selected:translate-x-[100%]" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ariaComponents.Text className="flex-1">
|
||||||
|
{getText('enableVersionChecker')}
|
||||||
|
</ariaComponents.Text>
|
||||||
|
</aria.Switch>
|
||||||
|
|
||||||
|
<ariaComponents.Text variant="body" color="disabled">
|
||||||
|
{getText('enableVersionCheckerDescription')}
|
||||||
|
</ariaComponents.Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ariaComponents.Separator orientation="horizontal" className="my-3" />
|
||||||
|
|
||||||
<ariaComponents.Text variant="subtitle" className="mb-2">
|
<ariaComponents.Text variant="subtitle" className="mb-2">
|
||||||
{getText('paywallDevtoolsPaywallFeaturesToggles')}
|
{getText('paywallDevtoolsPaywallFeaturesToggles')}
|
||||||
</ariaComponents.Text>
|
</ariaComponents.Text>
|
||||||
|
88
app/dashboard/src/layouts/VersionChecker.tsx
Normal file
88
app/dashboard/src/layouts/VersionChecker.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/** @file Check the version. */
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import { IS_DEV_MODE } from 'enso-common/src/detect'
|
||||||
|
|
||||||
|
import { useToastAndLog } from '#/hooks/toastAndLogHooks'
|
||||||
|
|
||||||
|
import { useLocalBackend } from '#/providers/BackendProvider'
|
||||||
|
import { useEnableVersionChecker } from '#/providers/EnsoDevtoolsProvider'
|
||||||
|
import { useText } from '#/providers/TextProvider'
|
||||||
|
|
||||||
|
import { Button, ButtonGroup, Dialog, Text } from '#/components/AriaComponents'
|
||||||
|
|
||||||
|
import { download } from '#/utilities/download'
|
||||||
|
import { getDownloadUrl, getLatestRelease, LATEST_RELEASE_PAGE_URL } from '#/utilities/github'
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// === VersionChecker ===
|
||||||
|
// ======================
|
||||||
|
|
||||||
|
/** Check the version. */
|
||||||
|
export default function VersionChecker() {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false)
|
||||||
|
const { getText } = useText()
|
||||||
|
const toastAndLog = useToastAndLog()
|
||||||
|
const localBackend = useLocalBackend()
|
||||||
|
const supportsLocalBackend = localBackend != null
|
||||||
|
const enableVersionChecker = useEnableVersionChecker() ?? (!IS_DEV_MODE && supportsLocalBackend)
|
||||||
|
|
||||||
|
const metadataQuery = useQuery({
|
||||||
|
queryKey: ['latestRelease', enableVersionChecker],
|
||||||
|
queryFn: () => (enableVersionChecker ? getLatestRelease() : null),
|
||||||
|
})
|
||||||
|
const latestVersion = metadataQuery.data?.tag_name
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (latestVersion != null && latestVersion !== process.env.ENSO_CLOUD_DASHBOARD_VERSION) {
|
||||||
|
setIsOpen(true)
|
||||||
|
}
|
||||||
|
}, [latestVersion])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
title={getText('versionOutdatedTitle')}
|
||||||
|
modalProps={{ isOpen }}
|
||||||
|
onOpenChange={setIsOpen}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Text className="text-center text-sm">{getText('versionOutdatedPrompt')}</Text>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<Text className="text-center text-sm">
|
||||||
|
{getText('yourVersion')}{' '}
|
||||||
|
<Text className="text-sm font-semibold text-danger">
|
||||||
|
{process.env.ENSO_CLOUD_DASHBOARD_VERSION ?? getText('unknownPlaceholder')}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text className="text-center text-sm">
|
||||||
|
{getText('latestVersion')}{' '}
|
||||||
|
<Text className="text-sm font-semibold text-accent">
|
||||||
|
{latestVersion ?? getText('unknownPlaceholder')}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<ButtonGroup className="justify-center">
|
||||||
|
<Button
|
||||||
|
size="medium"
|
||||||
|
variant="tertiary"
|
||||||
|
onPress={async () => {
|
||||||
|
const downloadUrl = await getDownloadUrl()
|
||||||
|
if (downloadUrl == null) {
|
||||||
|
toastAndLog('noAppDownloadError')
|
||||||
|
} else {
|
||||||
|
download(downloadUrl)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getText('download')}
|
||||||
|
</Button>
|
||||||
|
<Button size="medium" href={LATEST_RELEASE_PAGE_URL} target="_blank">
|
||||||
|
{getText('seeLatestRelease')}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
80
app/dashboard/src/providers/EnsoDevtoolsProvider.tsx
Normal file
80
app/dashboard/src/providers/EnsoDevtoolsProvider.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/** @file The React provider (and associated hooks) for Data Catalog state. */
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import invariant from 'tiny-invariant'
|
||||||
|
import * as zustand from 'zustand'
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// === EnsoDevtoolsStore ===
|
||||||
|
// =========================
|
||||||
|
|
||||||
|
/** The state of this zustand store. */
|
||||||
|
interface EnsoDevtoolsStore {
|
||||||
|
readonly showVersionChecker: boolean | null
|
||||||
|
readonly setEnableVersionChecker: (showVersionChecker: boolean | null) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
// =======================
|
||||||
|
// === ProjectsContext ===
|
||||||
|
// =======================
|
||||||
|
|
||||||
|
/** State contained in a `ProjectsContext`. */
|
||||||
|
export interface ProjectsContextType extends zustand.StoreApi<EnsoDevtoolsStore> {}
|
||||||
|
|
||||||
|
const EnsoDevtoolsContext = React.createContext<ProjectsContextType | null>(null)
|
||||||
|
|
||||||
|
/** Props for a {@link EnsoDevtoolsProvider}. */
|
||||||
|
export interface ProjectsProviderProps extends Readonly<React.PropsWithChildren> {}
|
||||||
|
|
||||||
|
// ========================
|
||||||
|
// === ProjectsProvider ===
|
||||||
|
// ========================
|
||||||
|
|
||||||
|
/** A React provider (and associated hooks) for determining whether the current area
|
||||||
|
* containing the current element is focused. */
|
||||||
|
export default function EnsoDevtoolsProvider(props: ProjectsProviderProps) {
|
||||||
|
const { children } = props
|
||||||
|
const [store] = React.useState(() => {
|
||||||
|
return zustand.createStore<EnsoDevtoolsStore>(set => ({
|
||||||
|
showVersionChecker: false,
|
||||||
|
setEnableVersionChecker: showVersionChecker => {
|
||||||
|
set({ showVersionChecker })
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
return <EnsoDevtoolsContext.Provider value={store}>{children}</EnsoDevtoolsContext.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// === useEnsoDevtoolsStore ===
|
||||||
|
// ============================
|
||||||
|
|
||||||
|
/** The Enso devtools store. */
|
||||||
|
function useEnsoDevtoolsStore() {
|
||||||
|
const store = React.useContext(EnsoDevtoolsContext)
|
||||||
|
|
||||||
|
invariant(store, 'Enso Devtools store can only be used inside an `EnsoDevtoolsProvider`.')
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===============================
|
||||||
|
// === useEnableVersionChecker ===
|
||||||
|
// ===============================
|
||||||
|
|
||||||
|
/** A function to set whether the version checker is forcibly shown/hidden. */
|
||||||
|
export function useEnableVersionChecker() {
|
||||||
|
const store = useEnsoDevtoolsStore()
|
||||||
|
return zustand.useStore(store, state => state.showVersionChecker)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================================
|
||||||
|
// === useSetEnableVersionChecker ===
|
||||||
|
// ==================================
|
||||||
|
|
||||||
|
/** A function to set whether the version checker is forcibly shown/hidden. */
|
||||||
|
export function useSetEnableVersionChecker() {
|
||||||
|
const store = useEnsoDevtoolsStore()
|
||||||
|
return zustand.useStore(store, state => state.setEnableVersionChecker)
|
||||||
|
}
|
@ -9,13 +9,14 @@ import * as detect from 'enso-common/src/detect'
|
|||||||
// =================
|
// =================
|
||||||
|
|
||||||
const ONE_HOUR_MS = 3_600_000
|
const ONE_HOUR_MS = 3_600_000
|
||||||
|
export const LATEST_RELEASE_PAGE_URL = 'https://github.com/enso-org/enso/releases/latest'
|
||||||
|
|
||||||
// ==================
|
// ==================
|
||||||
// === GitHub API ===
|
// === GitHub API ===
|
||||||
// ==================
|
// ==================
|
||||||
|
|
||||||
/** Metadata for a GitHub user. */
|
/** Metadata for a GitHub user. */
|
||||||
interface GithubSimpleUser {
|
interface GitHubSimpleUser {
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly email?: string
|
readonly email?: string
|
||||||
readonly login: string
|
readonly login: string
|
||||||
@ -59,7 +60,7 @@ interface GitHubReleaseAsset {
|
|||||||
readonly download_count: number
|
readonly download_count: number
|
||||||
readonly created_at: string
|
readonly created_at: string
|
||||||
readonly updated_at: string
|
readonly updated_at: string
|
||||||
readonly uploader?: GithubSimpleUser
|
readonly uploader?: GitHubSimpleUser
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Metadata for a GitHub release. */
|
/** Metadata for a GitHub release. */
|
||||||
@ -71,7 +72,7 @@ interface GitHubRelease {
|
|||||||
readonly tarball_url?: string
|
readonly tarball_url?: string
|
||||||
readonly zipball_url?: string
|
readonly zipball_url?: string
|
||||||
readonly id: number
|
readonly id: number
|
||||||
readonly author: GithubSimpleUser
|
readonly author: GitHubSimpleUser
|
||||||
readonly node_id: string
|
readonly node_id: string
|
||||||
/** The name of the tag. */
|
/** The name of the tag. */
|
||||||
readonly tag_name: string
|
readonly tag_name: string
|
||||||
@ -101,7 +102,7 @@ interface CachedRelease {
|
|||||||
const LOCAL_STORAGE_KEY = `${common.PRODUCT_NAME.toLowerCase()}-cached-release`
|
const LOCAL_STORAGE_KEY = `${common.PRODUCT_NAME.toLowerCase()}-cached-release`
|
||||||
|
|
||||||
/** Gets the metadata for the latest release of the app. */
|
/** Gets the metadata for the latest release of the app. */
|
||||||
async function getLatestRelease() {
|
export async function getLatestRelease() {
|
||||||
const savedCachedRelease = localStorage.getItem(LOCAL_STORAGE_KEY)
|
const savedCachedRelease = localStorage.getItem(LOCAL_STORAGE_KEY)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const cachedRelease: CachedRelease | null =
|
const cachedRelease: CachedRelease | null =
|
||||||
|
@ -189,6 +189,7 @@
|
|||||||
"views": "Views",
|
"views": "Views",
|
||||||
"likes": "Likes",
|
"likes": "Likes",
|
||||||
"shortcuts": "Shortcuts",
|
"shortcuts": "Shortcuts",
|
||||||
|
"download": "Download",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"emailIsRequired": "Email is required",
|
"emailIsRequired": "Email is required",
|
||||||
"emailIsInvalid": "Email is invalid",
|
"emailIsInvalid": "Email is invalid",
|
||||||
@ -226,6 +227,7 @@
|
|||||||
"metadata": "Metadata",
|
"metadata": "Metadata",
|
||||||
"path": "Path",
|
"path": "Path",
|
||||||
"reload": "Reload",
|
"reload": "Reload",
|
||||||
|
"seeLatestRelease": "See latest release",
|
||||||
|
|
||||||
"enterSecretPath": "Enter secret path",
|
"enterSecretPath": "Enter secret path",
|
||||||
"enterText": "Enter text",
|
"enterText": "Enter text",
|
||||||
@ -281,6 +283,7 @@
|
|||||||
"stopExecution": "Stop execution",
|
"stopExecution": "Stop execution",
|
||||||
"openInEditor": "Open in editor",
|
"openInEditor": "Open in editor",
|
||||||
|
|
||||||
|
"unknownPlaceholder": "unknown",
|
||||||
"expand": "Expand",
|
"expand": "Expand",
|
||||||
"collapse": "Collapse",
|
"collapse": "Collapse",
|
||||||
"sortAscending": "Sort Ascending",
|
"sortAscending": "Sort Ascending",
|
||||||
@ -303,12 +306,16 @@
|
|||||||
"cancelEdit": "Cancel Edit",
|
"cancelEdit": "Cancel Edit",
|
||||||
"loadingAppMessage": "Logging in to Enso...",
|
"loadingAppMessage": "Logging in to Enso...",
|
||||||
"appErroredMessage": "Enso encountered an unrecoverable error.",
|
"appErroredMessage": "Enso encountered an unrecoverable error.",
|
||||||
"appErroredPrompt": "Please try refreshing or installing an updateed version.",
|
"appErroredPrompt": "Please try refreshing or installing an updated version.",
|
||||||
"discoverWhatsNew": "Discover what’s new",
|
"discoverWhatsNew": "Discover what’s new",
|
||||||
"sampleAndCommunityProjects": "Sample and community projects",
|
"sampleAndCommunityProjects": "Sample and community projects",
|
||||||
"startWithATemplate": "Start with a template",
|
"startWithATemplate": "Start with a template",
|
||||||
"openInfoMenu": "Open info menu",
|
"openInfoMenu": "Open info menu",
|
||||||
"noProjectIsCurrentlyOpen": "No project is currently open.",
|
"noProjectIsCurrentlyOpen": "No project is currently open.",
|
||||||
|
"versionOutdatedTitle": "Upgrade Enso Now",
|
||||||
|
"versionOutdatedPrompt": "Download the latest version to get the latest upgrades and Cloud functionality.",
|
||||||
|
"yourVersion": "Your version:",
|
||||||
|
"latestVersion": "Latest version:",
|
||||||
"offlineTitle": "You are offline",
|
"offlineTitle": "You are offline",
|
||||||
"offlineErrorMessage": "It seems like you are offline. Please make sure you are connected to the internet and try again",
|
"offlineErrorMessage": "It seems like you are offline. Please make sure you are connected to the internet and try again",
|
||||||
"offlineToastMessage": "You are offline. Some features may be unavailable.",
|
"offlineToastMessage": "You are offline. Some features may be unavailable.",
|
||||||
@ -319,6 +326,7 @@
|
|||||||
"cloudUnavailableOfflineDescriptionOfferLocal": "Alternatively, you can work on your local projects.",
|
"cloudUnavailableOfflineDescriptionOfferLocal": "Alternatively, you can work on your local projects.",
|
||||||
"loginUnavailableOffline": "Login functionality is unavailable offline. Please connect to the internet to log in.",
|
"loginUnavailableOffline": "Login functionality is unavailable offline. Please connect to the internet to log in.",
|
||||||
"loginUnavailableOfflineLocal": "After logging in, you can work offline with your local projects.",
|
"loginUnavailableOfflineLocal": "After logging in, you can work offline with your local projects.",
|
||||||
|
"productionOnlyFeatures": "Production-only features",
|
||||||
"switchToLocal": "Switch to Local",
|
"switchToLocal": "Switch to Local",
|
||||||
"switchToCloud": "Switch to Cloud",
|
"switchToCloud": "Switch to Cloud",
|
||||||
"notEnabledTitle": "Your cloud experience is in progress",
|
"notEnabledTitle": "Your cloud experience is in progress",
|
||||||
@ -401,6 +409,8 @@
|
|||||||
"showPassword": "Show password",
|
"showPassword": "Show password",
|
||||||
"copiedToClipboard": "Copied to clipboard",
|
"copiedToClipboard": "Copied to clipboard",
|
||||||
"noResultsFound": "No results found.",
|
"noResultsFound": "No results found.",
|
||||||
|
"enableVersionChecker": "Enable Version Checker",
|
||||||
|
"enableVersionCheckerDescription": "Show a dialog if the current version of the desktop app does not match the latest version.",
|
||||||
|
|
||||||
"deleteLabelActionText": "delete the label '$0'",
|
"deleteLabelActionText": "delete the label '$0'",
|
||||||
"deleteSelectedAssetActionText": "delete '$0'",
|
"deleteSelectedAssetActionText": "delete '$0'",
|
||||||
|
Loading…
Reference in New Issue
Block a user