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
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 64 KiB |
46
src/App.tsx
@ -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()) {
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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">
|
||||
|