Change download-app banner to a toast & permanent button (#7010)

* pierremtb/issue6976-download-app-toast

* Cleaning up, more testing

* we goin places ft. new icon thanks @franknoirot

* Add app-version to masks

* Revert "Add app-version to masks"

This reverts commit 9624c3f434.

* Update dialog logic and snapshots

* Update snapssss

* Hook up settingsActor

* Polish

* Fix snap

* Quick fix, thanks bot

* Cleaning up linksssssssss
This commit is contained in:
Pierre Jacquier
2025-05-16 23:25:04 -04:00
committed by GitHub
parent 416de9a9fb
commit dc8496c62e
35 changed files with 155 additions and 85 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -28,6 +28,7 @@ import {
sceneInfra,
codeManager,
kclManager,
settingsActor,
} from '@src/lib/singletons'
import { maybeWriteToDisk } from '@src/lib/telemetry'
import type { IndexLoaderData } from '@src/lib/types'
@ -39,10 +40,19 @@ import { CommandBarOpenButton } from '@src/components/CommandBarOpenButton'
import { ShareButton } from '@src/components/ShareButton'
import {
needsToOnboard,
ONBOARDING_TOAST_ID,
TutorialRequestToast,
} from '@src/routes/Onboarding/utils'
import { reportRejection } from '@src/lib/trap'
import { DownloadAppToast } from '@src/components/DownloadAppBanner'
import openWindow from '@src/lib/openWindow'
import {
APP_DOWNLOAD_PATH,
CREATE_FILE_URL_PARAM,
DOWNLOAD_APP_TOAST_ID,
ONBOARDING_TOAST_ID,
} from '@src/lib/constants'
import { isPlaywright } from '@src/lib/isPlaywright'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
// CYCLIC REF
sceneInfra.camControls.engineStreamActor = engineStreamActor
@ -149,6 +159,40 @@ export function App() {
}
}, [location, settings.app.onboardingStatus, navigate])
useEffect(() => {
const needsDownloadAppToast =
!isDesktop() &&
!isPlaywright() &&
!searchParams.has(CREATE_FILE_URL_PARAM) &&
!settings.app.dismissWebBanner.current
if (needsDownloadAppToast) {
toast.success(
() =>
DownloadAppToast({
onAccept: () => {
openWindow(`${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}`)
.then(() => {
toast.dismiss(DOWNLOAD_APP_TOAST_ID)
})
.catch(reportRejection)
},
onDismiss: () => {
toast.dismiss(DOWNLOAD_APP_TOAST_ID)
settingsActor.send({
type: 'set.app.dismissWebBanner',
data: { level: 'user', value: true },
})
},
}),
{
id: DOWNLOAD_APP_TOAST_ID,
duration: Number.POSITIVE_INFINITY,
icon: null,
}
)
}
}, [])
// Only create the native file menus on desktop
useEffect(() => {
if (isDesktop()) {

View File

@ -11,7 +11,6 @@ import {
import { App } from '@src/App'
import { Auth } from '@src/Auth'
import { CommandBar } from '@src/components/CommandBar/CommandBar'
import DownloadAppBanner from '@src/components/DownloadAppBanner'
import { ErrorPage } from '@src/components/ErrorPage'
import FileMachineProvider from '@src/components/FileMachineProvider'
import ModelingMachineProvider from '@src/components/ModelingMachineProvider'
@ -84,10 +83,6 @@ const router = createRouter([
<Outlet />
<App />
<CommandBar />
{
// @ts-ignore
!isDesktop() && import.meta.env.PROD && <DownloadAppBanner />
}
</ModelingMachineProvider>
<WasmErrBanner />
</FileMachineProvider>

View File

@ -659,6 +659,22 @@ const CustomIconMap = {
/>
</svg>
),
download: (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5 15.5H15V12.5H16V16.5H4V12.5H5V15.5ZM10.5 11.293L13.1465 8.64648L13.8535 9.35352L10 13.207L6.14648 9.35352L6.85352 8.64648L9.5 11.293V3.5H10.5V11.293Z"
fill="currentColor"
/>
</svg>
),
'intersection-offset': (
<svg
viewBox="0 0 20 20"

View File

@ -1,32 +1,27 @@
import { Dialog } from '@headlessui/react'
import { useState } from 'react'
import { ActionButton } from '@src/components/ActionButton'
import { CREATE_FILE_URL_PARAM } from '@src/lib/constants'
import { useSettings } from '@src/lib/singletons'
import { useSearchParams } from 'react-router-dom'
import { Logo } from '@src/components/Logo'
const DownloadAppBanner = () => {
const [searchParams] = useSearchParams()
const settings = useSettings()
const [isBannerDismissed, setIsBannerDismissed] = useState(
searchParams.has(CREATE_FILE_URL_PARAM) ||
settings.app.dismissWebBanner.current
)
export type DownloadAppToastProps = {
onAccept: () => void
onDismiss: () => void
}
export function DownloadAppToast({
onAccept,
onDismiss,
}: DownloadAppToastProps) {
return (
<Dialog
className="fixed inset-0 z-50 grid place-items-center"
open={!isBannerDismissed}
onClose={() => ({})}
<div
data-testid="download-app-toast"
className="flex items-center gap-6 min-w-md"
>
<Dialog.Overlay className="fixed inset-0 bg-chalkboard-10/80 dark:bg-chalkboard-100/70" />
<Dialog.Panel className="relative max-w-xl bg-warn-20 text-warn-80 px-8 py-4 rounded-md">
<div className="max-w-3xl mx-auto">
<h2 className="text-2xl font-bold mb-4">Be warned!</h2>
<p>
Zoo Design Studio Desktop is more reliable! The web app is not
officially supported.
<Logo className="w-auto h-8 flex-none" />
<div className="flex flex-col justify-between gap-6 min-w-80">
<section>
<h2>Zoo Design Studio is primarily a desktop app</h2>
<p className="text-sm text-chalkboard-70 dark:text-chalkboard-30">
The present web app is limited in features. We don't want you to
miss out!
</p>
{!navigator?.userAgent.includes('Chrome') && (
<p className="mt-6">
@ -43,37 +38,31 @@ const DownloadAppBanner = () => {
to download it.
</p>
)}
<div className="flex flex-row-reverse gap-4 justify-between mt-6">
</section>
<div className="flex justify-between gap-8">
<ActionButton
Element="externalLink"
to="https://zoo.dev/modeling-app/download"
className="group !text-warn-10 pr-1 border-warn-70 hover:border-warn-80 dark:!border-warn-70 dark:hover:!border-warn-80 bg-warn-70 group-hover:bg-warn-80 dark:bg-warn-70 dark:group-hover:bg-warn-80"
iconEnd={{
icon: 'arrowRight',
iconClassName: 'text-warn-10 dark:text-warn-10',
bgClassName: '!bg-transparent',
Element="button"
iconStart={{
icon: 'close',
}}
data-negative-button="dismiss"
name="dismiss"
onClick={onDismiss}
>
Download Desktop App
Not right now
</ActionButton>
<ActionButton
Element="button"
onClick={() => setIsBannerDismissed(true)}
className="group text-warn-80 bg-warn-10 border-warn-50 hover:border-warn-80 hover:bg-warn-10 dark:bg-warn-10 dark:!border-warn-50 dark:hover:!border-warn-80 dark:text-warn-80 dark:hover:bg-warn-10"
iconStart={{
icon: 'checkmark',
iconClassName: 'text-warn-10 dark:text-warn-10',
bgClassName:
'bg-warn-50 group-hover:bg-warn-80 dark:bg-warn-50 dark:group-hover:bg-warn-80',
icon: 'download',
}}
name="accept"
onClick={onAccept}
>
Proceed at my own risk
Download the app
</ActionButton>
</div>
</div>
</Dialog.Panel>
</Dialog>
</div>
)
}
export default DownloadAppBanner

View File

@ -17,6 +17,9 @@ import { APP_VERSION, getReleaseUrl } from '@src/routes/utils'
import { billingActor } from '@src/lib/singletons'
import { ActionButton } from '@src/components/ActionButton'
import { isDesktop } from '@src/lib/isDesktop'
import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { APP_DOWNLOAD_PATH } from '@src/lib/constants'
export function LowerRightControls({
children,
@ -52,6 +55,7 @@ export function LowerRightControls({
<BillingDialog billingActor={billingActor} />
</Popover.Panel>
</Popover>
{isDesktop() ? (
<ActionButton
Element="externalLink"
to={getReleaseUrl()}
@ -62,6 +66,22 @@ export function LowerRightControls({
>
v{APP_VERSION}
</ActionButton>
) : (
<ActionButton
Element="externalLink"
to={`${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}`}
className={
'!no-underline !border-none !bg-transparent font-mono text-xs' +
linkOverrideClassName
}
iconStart={{
icon: 'download',
className: `w-5 h-5 !bg-transparent`,
}}
>
Download the app
</ActionButton>
)}
<Link
to={
location.pathname.includes(PATHS.FILE)

View File

@ -4,6 +4,7 @@ import { useSearchParams } from 'react-router-dom'
import { ActionButton } from '@src/components/ActionButton'
import {
APP_DOWNLOAD_PATH,
ASK_TO_OPEN_QUERY_PARAM,
ZOO_STUDIO_PROTOCOL,
} from '@src/lib/constants'
@ -120,7 +121,7 @@ export const OpenInDesktopAppHandler = (props: React.PropsWithChildren) => {
buttonClasses +
' text-sm border-transparent justify-center dark:bg-transparent'
}
to={`${VITE_KC_SITE_BASE_URL}/modeling-app/download`}
to={`${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}`}
iconEnd={{ icon: 'link', bgClassName: '!bg-transparent' }}
>
Download desktop app

View File

@ -3,6 +3,7 @@ import type { Models } from '@kittycad/lib/dist/types/src'
import type { UnitAngle, UnitLength } from '@rust/kcl-lib/bindings/ModelingCmd'
export const APP_NAME = 'Design Studio'
export const APP_DOWNLOAD_PATH = 'modeling-app/download'
/** Search string in new project names to increment as an index */
export const INDEX_IDENTIFIER = '$n'
/** The maximum number of 0's to pad a default project name's index with */
@ -126,6 +127,12 @@ export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast'
/** Toast id for the insert foreign part toast */
export const INSERT_FOREIGN_TOAST_ID = 'insert-foreign-toast'
/** Toast id for the onboarding */
export const ONBOARDING_TOAST_ID = 'onboarding-toast'
/** Toast id for the download app toast on web */
export const DOWNLOAD_APP_TOAST_ID = 'download-app-toast'
/** Local sketch axis values in KCL for operations, it could either be 'X' or 'Y' */
export const KCL_AXIS_X = 'X'
export const KCL_AXIS_Y = 'Y'

View File

@ -1,12 +1,12 @@
import type { SelectionRange } from '@codemirror/state'
import { EditorSelection, Transaction } from '@codemirror/state'
import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL } from '@src/env'
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env'
import { diffLines } from 'diff'
import toast from 'react-hot-toast'
import type { TextToCadMultiFileIteration_type } from '@kittycad/lib/dist/types/src/models'
import { getCookie, TOKEN_PERSIST_KEY } from '@src/machines/authMachine'
import { COOKIE_NAME } from '@src/lib/constants'
import { APP_DOWNLOAD_PATH, COOKIE_NAME } from '@src/lib/constants'
import { isDesktop } from '@src/lib/isDesktop'
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
import { ActionButton } from '@src/components/ActionButton'
@ -436,6 +436,7 @@ export async function promptToEditFlow({
return Promise.reject(result)
}
const oldCodeWebAppOnly = codeManager.code
const downloadLink = `${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}`
if (!isDesktop() && Object.values(result.outputs).length > 1) {
const toastId = uuidv4()
@ -447,13 +448,11 @@ export async function promptToEditFlow({
<div className="flex justify-between items-center mt-2">
<>
<a
href="https://zoo.dev/modeling-app/download"
href={downloadLink}
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 hover:text-blue-300 underline flex align-middle"
onClick={openExternalBrowserIfDesktop(
'https://zoo.dev/modeling-app/download'
)}
onClick={openExternalBrowserIfDesktop(downloadLink)}
>
<CustomIcon
name="link"

View File

@ -17,6 +17,7 @@ import {
ONBOARDING_DATA_ATTRIBUTE,
BROWSER_PROJECT_NAME,
PROJECT_ENTRYPOINT,
APP_DOWNLOAD_PATH,
} from '@src/lib/constants'
import { PATHS, joinRouterPaths } from '@src/lib/paths'
import type { Selections } from '@src/lib/selections'
@ -460,6 +461,7 @@ function PromptToEditResult() {
function OnboardingConclusion() {
// Close the panes on mount, close on unmount
useOnboardingPanes()
const downloadLink = `${VITE_KC_SITE_BASE_URL}/${APP_DOWNLOAD_PATH}`
return (
<div className="cursor-not-allowed fixed inset-0 z-50 p-16 grid justify-center items-center">
@ -468,10 +470,8 @@ function OnboardingConclusion() {
<p className="my-4">
We highly encourage you to{' '}
<a
onClick={openExternalBrowserIfDesktop(
`${VITE_KC_SITE_BASE_URL}/modeling-app/download/nightly`
)}
href="https://zoo.dev/modeling-app/download/nightly"
onClick={openExternalBrowserIfDesktop(downloadLink)}
href={downloadLink}
target="_blank"
rel="noopener noreferrer"
>

View File

@ -20,6 +20,7 @@ import { isKclEmptyOrOnlySettings } from '@src/lang/wasm'
import {
ONBOARDING_DATA_ATTRIBUTE,
ONBOARDING_PROJECT_NAME,
ONBOARDING_TOAST_ID,
} from '@src/lib/constants'
import toast from 'react-hot-toast'
import type CodeManager from '@src/lang/codeManager'
@ -342,8 +343,6 @@ export function needsToOnboard(
)
}
export const ONBOARDING_TOAST_ID = 'onboarding-toast'
export function onDismissOnboardingInvite() {
settingsActor.send({
type: 'set.app.onboardingStatus',
@ -370,7 +369,7 @@ export function TutorialRequestToast(props: OnboardingUtilDeps) {
return (
<div
data-testid="onboarding-toast"
className="flex items-center gap-6 min-w-80"
className="flex items-center gap-6 min-w-md"
>
<Logo className="w-auto h-8 flex-none" />
<div className="flex flex-col justify-between gap-6">