UX Papercuts 3: use absolute paths, add error page with buttons to help refresh, etc (#615)

* Fix #593: don't prevent default on link click

* Use absolute/explicit path for settings
Trying to test fix for #594

* Broken: replace almost all relative URLs with absolute

* Clean up to use clean useDismiss with absolute path

* Merge branch 'main' into franknoirot/ux-papercuts-3a

* Add buttons to home, reload, clear, and bug report on error screen
This commit is contained in:
Frank Noirot
2023-09-19 14:06:56 -04:00
committed by GitHub
parent 075d2debce
commit 6675fa8d1e
16 changed files with 92 additions and 72 deletions

View File

@ -130,6 +130,7 @@ const router = createBrowserRouter(
path: paths.INDEX,
loader: () =>
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
errorElement: <ErrorPage />,
},
{
path: paths.FILE + '/:id',
@ -140,7 +141,6 @@ const router = createBrowserRouter(
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
</Auth>
),
errorElement: <ErrorPage />,
id: paths.FILE,
loader: async ({
request,

View File

@ -1,4 +1,12 @@
import { useRouteError } from 'react-router-dom'
import { isTauri } from 'lib/isTauri'
import { useRouteError, isRouteErrorResponse } from 'react-router-dom'
import { ActionButton } from './ActionButton'
import {
faBug,
faHome,
faRefresh,
faTrash,
} from '@fortawesome/free-solid-svg-icons'
export const ErrorPage = () => {
let error = useRouteError()
@ -11,7 +19,43 @@ export const ErrorPage = () => {
<h1 className="text-4xl mb-8 font-bold">
An unexpected error occurred
</h1>
<p>{String(error)}</p>
{isRouteErrorResponse(error) && (
<p className="mb-8">
{error.status}: {error.data}
</p>
)}
<div className="flex justify-between gap-2 mt-6">
{isTauri() && (
<ActionButton Element="link" to={'/'} icon={{ icon: faHome }}>
Go Home
</ActionButton>
)}
<ActionButton
Element="button"
icon={{ icon: faRefresh }}
onClick={() => window.location.reload()}
>
Reload
</ActionButton>
<ActionButton
Element="button"
icon={{ icon: faTrash }}
onClick={() => {
window.localStorage.clear()
}}
>
Clear storage
</ActionButton>
<ActionButton
Element="link"
icon={{ icon: faBug }}
target="_blank"
rel="noopener noreferrer"
to="https://discord.com/channels/915388055236509727/1138967922614743060"
>
Report Bug
</ActionButton>
</div>
</section>
</div>
)

View File

@ -9,7 +9,6 @@ import {
cameraMouseDragGuards,
cameraSystems,
} from 'lib/cameraControls'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function Units() {
const { buttonDownInStream } = useStore((s) => ({
@ -25,7 +24,6 @@ export default function Units() {
},
},
} = useGlobalStateContext()
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
@ -74,7 +72,7 @@ export default function Units() {
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -2,7 +2,6 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function CmdK() {
const { buttonDownInStream } = useStore((s) => ({
@ -10,7 +9,6 @@ export default function CmdK() {
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.USER_MENU)
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
@ -43,7 +41,7 @@ export default function CmdK() {
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -3,7 +3,6 @@ import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function CodeEditor() {
const { buttonDownInStream } = useStore((s) => ({
@ -11,7 +10,6 @@ export default function CodeEditor() {
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.PARAMETRIC_MODELING)
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
@ -62,7 +60,7 @@ export default function CodeEditor() {
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -2,7 +2,6 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function Export() {
const { buttonDownInStream } = useStore((s) => ({
@ -10,7 +9,6 @@ export default function Export() {
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.SKETCHING)
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
@ -42,7 +40,7 @@ export default function Export() {
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -4,14 +4,12 @@ import { useDismiss } from '.'
import { useEffect } from 'react'
import { useStore } from 'useStore'
import { bracket } from 'lib/exampleKcl'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function FutureWork() {
const dismiss = useDismiss()
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
const dotDotSlash = useDotDotSlash()
useEffect(() => {
deferredSetCode(bracket)
@ -36,7 +34,7 @@ export default function FutureWork() {
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
@ -49,7 +47,7 @@ export default function FutureWork() {
</ActionButton>
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{ icon: faArrowRight }}
>
Finish

View File

@ -3,7 +3,6 @@ import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function InteractiveNumbers() {
const { buttonDownInStream } = useStore((s) => ({
@ -11,7 +10,6 @@ export default function InteractiveNumbers() {
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.COMMAND_K)
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
@ -102,7 +100,7 @@ export default function InteractiveNumbers() {
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -15,15 +15,13 @@ import { isTauri } from 'lib/isTauri'
import { useNavigate } from 'react-router-dom'
import { paths } from 'Router'
import { useEffect } from 'react'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
function OnboardingWithNewFile() {
const navigate = useNavigate()
const dotDotSlash = useDotDotSlash()
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.INDEX)
const { setCode } = useStore((s) => ({
setCode: s.setCode,
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
const {
settings: {
@ -53,7 +51,7 @@ function OnboardingWithNewFile() {
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash())}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
@ -67,7 +65,7 @@ function OnboardingWithNewFile() {
<ActionButton
Element="button"
onClick={() => {
setCode(bracket)
deferredSetCode(bracket)
next()
}}
icon={{ icon: faArrowRight }}
@ -91,7 +89,7 @@ function OnboardingWithNewFile() {
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash())}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
@ -118,9 +116,9 @@ function OnboardingWithNewFile() {
}
export default function Introduction() {
const { setCode, code } = useStore((s) => ({
const { deferredSetCode, code } = useStore((s) => ({
code: s.code,
setCode: s.setCode,
deferredSetCode: s.deferredSetCode,
}))
const {
settings: {
@ -136,11 +134,10 @@ export default function Introduction() {
: ''
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.CAMERA)
const dotDotSlash = useDotDotSlash()
useEffect(() => {
if (code === '') setCode(bracket)
}, [code, setCode])
if (code === '') deferredSetCode(bracket)
}, [code, deferredSetCode])
return !(code !== '' && code !== bracket) ? (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
@ -180,7 +177,7 @@ export default function Introduction() {
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash())}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -5,7 +5,6 @@ import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
import { Themes, getSystemTheme } from 'lib/theme'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function ParametricModeling() {
const { buttonDownInStream } = useStore((s) => ({
@ -23,7 +22,6 @@ export default function ParametricModeling() {
: ''
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.INTERACTIVE_NUMBERS)
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
@ -62,7 +60,7 @@ export default function ParametricModeling() {
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -3,7 +3,6 @@ import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { isTauri } from 'lib/isTauri'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function ProjectMenu() {
const { buttonDownInStream } = useStore((s) => ({
@ -11,7 +10,6 @@ export default function ProjectMenu() {
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.EXPORT)
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
@ -33,7 +31,7 @@ export default function ProjectMenu() {
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -3,7 +3,6 @@ import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from 'useStore'
import { useEffect } from 'react'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function Sketching() {
const { deferredSetCode, buttonDownInStream } = useStore((s) => ({
@ -16,7 +15,6 @@ export default function Sketching() {
useEffect(() => {
deferredSetCode('')
}, [deferredSetCode])
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
@ -40,7 +38,7 @@ export default function Sketching() {
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -2,7 +2,6 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function Streaming() {
const { buttonDownInStream } = useStore((s) => ({
@ -10,7 +9,6 @@ export default function Streaming() {
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.EDITOR)
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid justify-start items-center inset-0 z-50 pointer-events-none">
@ -43,7 +41,7 @@ export default function Streaming() {
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -6,7 +6,6 @@ import { Toggle } from '../../components/Toggle/Toggle'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { UnitSystem } from 'machines/settingsMachine'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function Units() {
const dismiss = useDismiss()
@ -17,7 +16,6 @@ export default function Units() {
context: { unitSystem, baseUnit },
},
} = useGlobalStateContext()
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
@ -68,7 +66,7 @@ export default function Units() {
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -2,7 +2,6 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export default function UserMenu() {
const { buttonDownInStream } = useStore((s) => ({
@ -10,7 +9,6 @@ export default function UserMenu() {
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.PROJECT_MENU)
const dotDotSlash = useDotDotSlash()
return (
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
@ -30,7 +28,7 @@ export default function UserMenu() {
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => dismiss(dotDotSlash(2))}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -1,5 +1,5 @@
import { useHotkeys } from 'react-hotkeys-hook'
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
import { Outlet, useRouteLoaderData, useNavigate } from 'react-router-dom'
import Introduction from './Introduction'
import Camera from './Camera'
import Sketching from './Sketching'
@ -15,6 +15,7 @@ import UserMenu from './UserMenu'
import ProjectMenu from './ProjectMenu'
import Export from './Export'
import FutureWork from './FutureWork'
import { IndexLoaderData, paths } from 'Router'
export const onboardingPaths = {
INDEX: '/',
@ -89,42 +90,44 @@ export function useNextClick(newStatus: string) {
settings: { send },
} = useGlobalStateContext()
const navigate = useNavigate()
const location = useLocation()
const lastSlashIndex = location.pathname.lastIndexOf('/')
const { project } = useRouteLoaderData(paths.FILE) as IndexLoaderData
return useCallback(() => {
send({
type: 'Set Onboarding Status',
data: { onboardingStatus: newStatus },
})
navigate(location.pathname.slice(0, lastSlashIndex) + newStatus)
}, [location, lastSlashIndex, newStatus, send, navigate])
navigate(
paths.FILE +
'/' +
encodeURIComponent(project?.path || 'new') +
paths.ONBOARDING.INDEX.slice(0, -1) +
newStatus
)
}, [project, newStatus, send, navigate])
}
export function useDismiss() {
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
const {
settings: { send },
} = useGlobalStateContext()
const navigate = useNavigate()
return useCallback(
(path: string) => {
return useCallback(() => {
send({
type: 'Set Onboarding Status',
data: { onboardingStatus: 'dismissed' },
})
console.log('yoyo', window.location.pathname, path)
navigate(path)
},
[send, navigate]
navigate(
paths.FILE + '/' + encodeURIComponent(routeData?.project?.path || 'new')
)
}, [send, navigate, routeData])
}
const Onboarding = () => {
const location = useLocation()
const dismiss = useDismiss()
const lastSlashIndex = location.pathname.lastIndexOf('/')
useHotkeys('esc', () => dismiss(location.pathname.slice(0, lastSlashIndex)))
useHotkeys('esc', dismiss)
return (
<>