Update onboarding to be more complete (#551)
* Update Introduction * Update Camera step * Change link to expectations Co-authored-by: Josh Gomez <114548659+jgomez720@users.noreply.github.com> * Set outline for onboarding * Add Streaming step * Remove Units step * Add default kcl script * Add Code Editor step * Add Parametric Modeling step * Add Interactive Numbers step * Update bracket to use sqrt * Add Command K step * Assuage @jessfraz's code itchies * Add User Menu step * Add Project Menu step * Add Export step * Improve error page to actually show error * Update the sketch step * Add Future Work section * Bring back the bracket code on the final step * Set up the code to the bracket when starting onboarding * Fix missing import * Don't throw away users code if not empty * Prompt the user if they have content in their file --------- Co-authored-by: Josh Gomez <114548659+jgomez720@users.noreply.github.com>
This commit is contained in:
46
public/kcma-logomark-dark.svg
Normal file
46
public/kcma-logomark-dark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
46
public/kcma-logomark.svg
Normal file
46
public/kcma-logomark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
BIN
public/onboarding-bracket-dark.png
Normal file
BIN
public/onboarding-bracket-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 148 KiB |
BIN
public/onboarding-bracket.png
Normal file
BIN
public/onboarding-bracket.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
@ -117,8 +117,9 @@ export function App() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const paneOpacity =
|
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
|
||||||
onboardingStatus === onboardingPaths.CAMERA
|
(p) => p === onboardingStatus
|
||||||
|
)
|
||||||
? 'opacity-20'
|
? 'opacity-20'
|
||||||
: didDragInStream
|
: didDragInStream
|
||||||
? 'opacity-40'
|
? 'opacity-40'
|
||||||
@ -255,7 +256,7 @@ export function App() {
|
|||||||
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
|
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="h-full flex flex-col justify-between">
|
<div id="code-pane" className="h-full flex flex-col justify-between">
|
||||||
<CollapsiblePanel
|
<CollapsiblePanel
|
||||||
title="Code"
|
title="Code"
|
||||||
icon={faCode}
|
icon={faCode}
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
|
import { useRouteError } from 'react-router-dom'
|
||||||
|
|
||||||
export const ErrorPage = () => {
|
export const ErrorPage = () => {
|
||||||
|
let error = useRouteError()
|
||||||
|
|
||||||
|
console.error('error', error)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-screen">
|
<div className="flex flex-col items-center justify-center h-screen">
|
||||||
<h1 className="text-4xl font-bold">404</h1>
|
<section className="max-w-full xl:max-w-4xl mx-auto">
|
||||||
<p className="text-2xl font-bold">Page not found</p>
|
<h1 className="text-4xl mb-8 font-bold">
|
||||||
|
An unexpected error occurred
|
||||||
|
</h1>
|
||||||
|
<p>{String(error)}</p>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
21
src/lib/exampleKcl.ts
Normal file
21
src/lib/exampleKcl.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export const bracket = `// Material: 6061-T6 Aluminum
|
||||||
|
const sigmaAllow = 35000 // psi
|
||||||
|
const width = 9 // inch
|
||||||
|
const p = 150 // Force on shelf - lbs
|
||||||
|
const distance = 6 // inches
|
||||||
|
const FOS = 2
|
||||||
|
|
||||||
|
const leg1 = 5 // inches
|
||||||
|
const leg2 = 8 // inches
|
||||||
|
const thickness = sqrt(distance * p * FOS * 6 / sigmaAllow / width) // inches
|
||||||
|
const bracket = startSketchAt([0, 0])
|
||||||
|
|> line([0, leg1], %)
|
||||||
|
|> line([leg2, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-leg2 + thickness, 0], %)
|
||||||
|
|> line([0, -leg1 + thickness], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(width, %)
|
||||||
|
|
||||||
|
show(bracket)
|
||||||
|
`
|
@ -2,13 +2,28 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
|||||||
import { ActionButton } from '../../components/ActionButton'
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
import { useStore } from '../../useStore'
|
import { useStore } from '../../useStore'
|
||||||
|
import { SettingsSection } from 'routes/Settings'
|
||||||
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
import {
|
||||||
|
CameraSystem,
|
||||||
|
cameraMouseDragGuards,
|
||||||
|
cameraSystems,
|
||||||
|
} from 'lib/cameraControls'
|
||||||
|
|
||||||
export default function Units() {
|
export default function Units() {
|
||||||
const { buttonDownInStream } = useStore((s) => ({
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
buttonDownInStream: s.buttonDownInStream,
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
}))
|
}))
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const next = useNextClick(onboardingPaths.SKETCHING)
|
const next = useNextClick(onboardingPaths.STREAMING)
|
||||||
|
const {
|
||||||
|
settings: {
|
||||||
|
send,
|
||||||
|
state: {
|
||||||
|
context: { cameraControls },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
||||||
@ -18,29 +33,43 @@ export default function Units() {
|
|||||||
(buttonDownInStream ? '' : ' pointer-events-auto')
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<h1 className="text-2xl font-bold">Camera</h1>
|
<SettingsSection
|
||||||
<p className="mt-6">
|
title="Camera Controls"
|
||||||
Moving the camera is easy! The controls are as you might expect:
|
description="How you want to control the camera in the 3D view. Try them out in the 3D view."
|
||||||
</p>
|
>
|
||||||
<ul className="list-disc list-outside ms-8 mb-4">
|
<select
|
||||||
<li>Click and drag anywhere in the scene to rotate the camera</li>
|
id="camera-controls"
|
||||||
|
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
||||||
|
value={cameraControls}
|
||||||
|
onChange={(e) => {
|
||||||
|
send({
|
||||||
|
type: 'Set Camera Controls',
|
||||||
|
data: { cameraControls: e.target.value as CameraSystem },
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cameraSystems.map((program) => (
|
||||||
|
<option key={program} value={program}>
|
||||||
|
{program}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<ul className="text-sm my-2 mx-4 leading-relaxed">
|
||||||
<li>
|
<li>
|
||||||
Hold down the <kbd>Shift</kbd> key while clicking and dragging to
|
<strong>Pan:</strong>{' '}
|
||||||
pan the camera
|
{cameraMouseDragGuards[cameraControls].pan.description}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Hold down the <kbd>Ctrl</kbd> key while dragging to zoom. You can
|
<strong>Zoom:</strong>{' '}
|
||||||
also use the scroll wheel to zoom in and out.
|
{cameraMouseDragGuards[cameraControls].zoom.description}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Rotate:</strong>{' '}
|
||||||
|
{cameraMouseDragGuards[cameraControls].rotate.description}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
</SettingsSection>
|
||||||
What you're seeing here is just a video, and your interactions are
|
<div className="flex justify-between">
|
||||||
being sent to our Geometry Engine API, which sends back video frames
|
|
||||||
in real time. How cool is that? It means that you can use KittyCAD
|
|
||||||
Modeling App (or whatever you want to build) on any device, even a
|
|
||||||
cheap laptop with no graphics card!
|
|
||||||
</p>
|
|
||||||
<div className="flex justify-between mt-6">
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => dismiss('../../')}
|
onClick={() => dismiss('../../')}
|
||||||
@ -59,7 +88,7 @@ export default function Units() {
|
|||||||
onClick={next}
|
onClick={next}
|
||||||
icon={{ icon: faArrowRight }}
|
icon={{ icon: faArrowRight }}
|
||||||
>
|
>
|
||||||
Next: Sketching
|
Next: Streaming
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
66
src/routes/Onboarding/CmdK.tsx
Normal file
66
src/routes/Onboarding/CmdK.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
|
||||||
|
export default function CmdK() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.USER_MENU)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-full xl:max-w-4xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<h2 className="text-2xl">Command Bar</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
Press <kbd>Cmd/Win</kbd> + <kbd>K</kbd> to open the command bar. Try
|
||||||
|
changing your camera controls with it.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
We are working on a command bar that will allow you to quickly see and
|
||||||
|
search for any available commands. We are building KittyCAD Modeling
|
||||||
|
App's state management system on top of{' '}
|
||||||
|
<a
|
||||||
|
href="https://xstate.js.org/"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
XState
|
||||||
|
</a>
|
||||||
|
. Currently you can only control settings, authentication, and file
|
||||||
|
management from the command bar, but we will be powering modeling
|
||||||
|
commands with it soon.
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: User Menu
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
85
src/routes/Onboarding/CodeEditor.tsx
Normal file
85
src/routes/Onboarding/CodeEditor.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||||
|
|
||||||
|
export default function CodeEditor() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.PARAMETRIC_MODELING)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
|
||||||
|
style={{ clipPath: useBackdropHighlight('code-pane') }}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1">
|
||||||
|
<h2 className="text-2xl">
|
||||||
|
Editing code with <code>kcl</code>
|
||||||
|
</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
The left pane is where you write your code. It's a code editor with
|
||||||
|
syntax highlighting and autocompletion. We've decided to take the
|
||||||
|
difficult route of writing our own language—called <code>kcl</code>
|
||||||
|
—for describing geometry, because don't want to inherit all the
|
||||||
|
other functionality from existing languages. We have a lot of ideas
|
||||||
|
about how <code>kcl</code> will evolve, and we want to hear your
|
||||||
|
thoughts on it.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
We've built a language server for <code>kcl</code> that provides
|
||||||
|
documentation and autocompletion automatically generated from our
|
||||||
|
compiler code. You can try it out by hovering over some of the
|
||||||
|
function names in the pane now. If you like using VSCode, you can
|
||||||
|
try out our{' '}
|
||||||
|
<a
|
||||||
|
href="https://marketplace.visualstudio.com/items?itemName=KittyCAD.kcl-language-server"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
VSCode extension
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
You can resize the pane by dragging the handle on the right, and you
|
||||||
|
can collapse it by clicking the title bar or pressing{' '}
|
||||||
|
<kbd>Shift</kbd> + <kbd>C</kbd>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Parametric Modeling
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
65
src/routes/Onboarding/Export.tsx
Normal file
65
src/routes/Onboarding/Export.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
|
||||||
|
export default function Export() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.SKETCHING)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1">
|
||||||
|
<h2 className="text-2xl">Export</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
Try opening the project menu and clicking "Export Model".
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
KittyCAD Modeling App uses our open-source extension proposal for
|
||||||
|
the GLTF file format.{' '}
|
||||||
|
<a
|
||||||
|
href="https://kittycad.io/docs/api/convert-cad-file"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Our conversion API
|
||||||
|
</a>{' '}
|
||||||
|
can convert to and from most common CAD file formats, allowing
|
||||||
|
export to almost any CAD software.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Sketching
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
59
src/routes/Onboarding/FutureWork.tsx
Normal file
59
src/routes/Onboarding/FutureWork.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { useDismiss } from '.'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
|
||||||
|
export default function FutureWork() {
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const { setCode } = useStore((s) => ({
|
||||||
|
setCode: s.setCode,
|
||||||
|
}))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCode(bracket)
|
||||||
|
}, [setCode])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
|
||||||
|
<div className="max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
||||||
|
<h1 className="text-2xl font-bold">Future Work</h1>
|
||||||
|
<p className="my-4">
|
||||||
|
We have curves, cuts, and many more CAD features coming soon. We want
|
||||||
|
your feedback on this user interface, and we want to know what
|
||||||
|
features you want to see next. Please message us in the Discord server
|
||||||
|
and open issues on GitHub.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
If you make anything with the app we'd love to see it! Thank you for
|
||||||
|
taking time to try out KittyCAD Modeling App, and build the future of
|
||||||
|
hardware design with us 💚.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">— The KittyCAD Team</p>
|
||||||
|
<div className="flex justify-between mt-6">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../../')}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Finish
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
125
src/routes/Onboarding/InteractiveNumbers.tsx
Normal file
125
src/routes/Onboarding/InteractiveNumbers.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||||
|
|
||||||
|
export default function InteractiveNumbers() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.COMMAND_K)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
|
||||||
|
style={{ clipPath: useBackdropHighlight('code-pane') }}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1 overflow-y-auto mb-6">
|
||||||
|
<h2 className="text-2xl">Interactive Numbers</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
Let's do a little bit of hybrid editing to this part.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
Try changing the value of <code>width</code> on line 3 by holding
|
||||||
|
the <kbd>Alt</kbd> key and dragging the number left and right. You
|
||||||
|
can hold down different modifier keys to change the value by
|
||||||
|
different increments:
|
||||||
|
<table className="border-collapse text-sm mx-auto my-4">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
|
||||||
|
<kbd>Alt + Shift + Cmd/Win</kbd>
|
||||||
|
</td>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
|
||||||
|
0.01
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
|
||||||
|
<kbd>Alt + Cmd/Win</kbd>
|
||||||
|
</td>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
|
||||||
|
0.1
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
|
||||||
|
<kbd>Alt</kbd>
|
||||||
|
</td>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
|
||||||
|
1
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
|
||||||
|
<kbd>Alt + Shift</kbd>
|
||||||
|
</td>
|
||||||
|
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
|
||||||
|
10
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
Our code editor is built with{' '}
|
||||||
|
<a
|
||||||
|
href="https://codemirror.net/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopeneer"
|
||||||
|
>
|
||||||
|
CodeMirror
|
||||||
|
</a>
|
||||||
|
, a great open-source project with extensions that make it even more
|
||||||
|
dynamic and interactive, including{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/replit/codemirror-interact/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopeneer"
|
||||||
|
>
|
||||||
|
one by the Replit team
|
||||||
|
</a>{' '}
|
||||||
|
lets you interact with numbers in your code by dragging them around.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
Editing code should feel as interactive as point-and-click when you
|
||||||
|
want it to be, so that you can work in the way that feels most
|
||||||
|
natural to you. We're going to keep extending the text editor, and
|
||||||
|
we'd love to hear your ideas for how to make it better.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Command Bar
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,25 +1,175 @@
|
|||||||
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionButton } from '../../components/ActionButton'
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
import { Themes } from 'lib/theme'
|
||||||
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
import {
|
||||||
|
createNewProject,
|
||||||
|
getNextProjectIndex,
|
||||||
|
getProjectsInDir,
|
||||||
|
interpolateProjectNameWithIndex,
|
||||||
|
} from 'lib/tauriFS'
|
||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { paths } from 'Router'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
export default function Introduction() {
|
function OnboardingWithNewFile() {
|
||||||
|
const navigate = useNavigate()
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const next = useNextClick(onboardingPaths.UNITS)
|
const next = useNextClick(onboardingPaths.INDEX)
|
||||||
|
const { setCode, code } = useStore((s) => ({
|
||||||
|
code: s.code,
|
||||||
|
setCode: s.setCode,
|
||||||
|
}))
|
||||||
|
const {
|
||||||
|
settings: {
|
||||||
|
context: { defaultDirectory, defaultProjectName },
|
||||||
|
},
|
||||||
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
|
async function createAndOpenNewProject() {
|
||||||
|
const projects = await getProjectsInDir(defaultDirectory)
|
||||||
|
const nextIndex = await getNextProjectIndex(defaultProjectName, projects)
|
||||||
|
const name = interpolateProjectNameWithIndex(defaultProjectName, nextIndex)
|
||||||
|
const newFile = await createNewProject(defaultDirectory + '/' + name)
|
||||||
|
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
||||||
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
||||||
<h1 className="text-2xl font-bold">
|
{!isTauri() ? (
|
||||||
Welcome to the KittyCAD Modeling App
|
<>
|
||||||
|
<h1 className="text-2xl font-bold text-warn-80 dark:text-warn-10">
|
||||||
|
Replaying onboarding resets your code
|
||||||
</h1>
|
</h1>
|
||||||
<p className="my-2">
|
<p className="my-4">
|
||||||
A browser-first, GPU-streaming hardware design tool that lets you edit
|
We see you have some of your own code written in this project.
|
||||||
visually, with code, or both.
|
Please save it somewhere else before continuing the onboarding.
|
||||||
</p>
|
</p>
|
||||||
<p className="my-2">
|
<div className="flex justify-between mt-6">
|
||||||
Powered by the first API created for anyone to build hardware design
|
<ActionButton
|
||||||
tools.
|
Element="button"
|
||||||
|
onClick={() => dismiss('../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => {
|
||||||
|
setCode(bracket)
|
||||||
|
next()
|
||||||
|
}}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Overwrite code and continue
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
|
||||||
|
Would you like to create a new project?
|
||||||
|
</h1>
|
||||||
|
<section className="my-12">
|
||||||
|
<p className="my-4">
|
||||||
|
You have some content in this project that we don't want to
|
||||||
|
overwrite. If you would like to create a new project, please
|
||||||
|
click the button below.
|
||||||
</p>
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between mt-6">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={createAndOpenNewProject}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Make a new project
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Introduction() {
|
||||||
|
const { setCode, code } = useStore((s) => ({
|
||||||
|
code: s.code,
|
||||||
|
setCode: s.setCode,
|
||||||
|
}))
|
||||||
|
const {
|
||||||
|
settings: {
|
||||||
|
state: {
|
||||||
|
context: { theme },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = useGlobalStateContext()
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.CAMERA)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (code === '') setCode(bracket)
|
||||||
|
}, [code, setCode])
|
||||||
|
|
||||||
|
return !(code !== '' && code !== bracket) ? (
|
||||||
|
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
||||||
|
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
||||||
|
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
|
||||||
|
<img
|
||||||
|
src={`/kcma-logomark${theme === Themes.Light ? '-dark' : ''}.svg`}
|
||||||
|
alt="KittyCAD Modeling App"
|
||||||
|
className="max-w-full h-20"
|
||||||
|
/>
|
||||||
|
<span className="bg-energy-10 text-energy-80 px-3 py-1 rounded-full text-base">
|
||||||
|
Alpha
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<section className="my-12">
|
||||||
|
<p className="my-4">
|
||||||
|
Welcome to KittyCAD Modeling App! This is a hardware design tool
|
||||||
|
that lets you edit visually, with code, or both. It's powered by the
|
||||||
|
first API created for anyone to build hardware design tools. The 3D
|
||||||
|
view is not running on your computer, but is instead being streamed
|
||||||
|
to you from a remote GPU as video.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
This is an alpha release, so you will encounter bugs and missing
|
||||||
|
features. You can read our{' '}
|
||||||
|
<a
|
||||||
|
href="https://gist.github.com/jgomez720/5cd53fb7e8e54079f6dc0d2625de5393"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
expectations for alpha users here
|
||||||
|
</a>
|
||||||
|
. Please give us feedback on your experience! We are trying to
|
||||||
|
release as early as possible to get feedback from users like you.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
<div className="flex justify-between mt-6">
|
<div className="flex justify-between mt-6">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
@ -44,5 +194,7 @@ export default function Introduction() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<OnboardingWithNewFile />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
82
src/routes/Onboarding/ParametricModeling.tsx
Normal file
82
src/routes/Onboarding/ParametricModeling.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||||
|
import { Themes } from 'lib/theme'
|
||||||
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
|
||||||
|
export default function ParametricModeling() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const {
|
||||||
|
settings: {
|
||||||
|
context: { theme },
|
||||||
|
},
|
||||||
|
} = useGlobalStateContext()
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.INTERACTIVE_NUMBERS)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
|
||||||
|
style={{ clipPath: useBackdropHighlight('code-pane') }}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1 overflow-y-auto mb-6">
|
||||||
|
<h2 className="text-2xl">Towards true parametric modeling</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
This example script shows how having access to the code
|
||||||
|
representation of a part can allow us to do things that are tedious
|
||||||
|
or impossible in traditional CAD software. Here we are building a
|
||||||
|
simplified shelf bracket out of aluminum:
|
||||||
|
</p>
|
||||||
|
<figure className="my-4 w-3/4 mx-auto">
|
||||||
|
<img
|
||||||
|
src={`/onboarding-bracket${
|
||||||
|
theme === Themes.Light ? '-dark' : ''
|
||||||
|
}.png`}
|
||||||
|
alt="Bracket"
|
||||||
|
/>
|
||||||
|
<figcaption className="text-small italic text-center">
|
||||||
|
A simplified shelf bracket
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
<p className="my-4">
|
||||||
|
We are able to easily calculate the thickness of the material based
|
||||||
|
on the width of the bracket to meet a set safety factor on line 13.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Interactive Numbers
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
56
src/routes/Onboarding/ProjectMenu.tsx
Normal file
56
src/routes/Onboarding/ProjectMenu.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
import { isTauri } from 'lib/isTauri'
|
||||||
|
|
||||||
|
export default function ProjectMenu() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.EXPORT)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1">
|
||||||
|
<h2 className="text-2xl">Project Menu</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
Click on Kitt in the upper left to open the project menu. You can
|
||||||
|
only {isTauri() && 'go home or '}export your model—which we'll talk
|
||||||
|
about next—for now. We'll add more options here soon, especially as
|
||||||
|
we add support for multi-file assemblies.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Export
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,16 +1,39 @@
|
|||||||
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionButton } from '../../components/ActionButton'
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
import { useDismiss } from '.'
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from 'useStore'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
export default function Sketching() {
|
export default function Sketching() {
|
||||||
|
const { setCode, buttonDownInStream } = useStore((s) => ({
|
||||||
|
setCode: s.setCode,
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.FUTURE_WORK)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCode('')
|
||||||
|
}, [setCode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-center items-end inset-0 bg-chalkboard-110/50 z-50">
|
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
||||||
<div className="max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
<h1 className="text-2xl font-bold">Sketching</h1>
|
<h1 className="text-2xl font-bold">Sketching</h1>
|
||||||
<p className="mt-6">
|
<p className="my-4">
|
||||||
We still have to implement this step, and the rest of the tutorial!
|
Our 3D modeling tools are still very much a work in progress, but we
|
||||||
|
want to show you some early features. Try creating a sketch by
|
||||||
|
clicking Create Sketch in the top toolbar, then clicking the Line
|
||||||
|
tool, and clicking in the 3D view.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
Watch the code pane as you click. Point-and-click interactions are
|
||||||
|
always just modifying and generating code in KittyCAD Modeling App.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-between mt-6">
|
<div className="flex justify-between mt-6">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
@ -28,10 +51,10 @@ export default function Sketching() {
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => dismiss('../../')}
|
onClick={next}
|
||||||
icon={{ icon: faArrowRight }}
|
icon={{ icon: faArrowRight }}
|
||||||
>
|
>
|
||||||
Finish
|
Next: Future Work
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
66
src/routes/Onboarding/Streaming.tsx
Normal file
66
src/routes/Onboarding/Streaming.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
|
||||||
|
export default function Streaming() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.EDITOR)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-start items-center inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1">
|
||||||
|
<h2 className="text-2xl">Streaming Video</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
The 3D view is not running on your computer. Instead, our
|
||||||
|
infrastructure spins up the KittyCAD Geometry Engine on a remote
|
||||||
|
GPU, KittyCAD Modeling App sends it a series of commands via
|
||||||
|
Websockets and WebRTC, and the Geometry Engine sends back a video
|
||||||
|
stream of the 3D view.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
This means that you could run KittyCAD Modeling App on a Chromebook,
|
||||||
|
a tablet, or even a phone, as long as you have a good internet
|
||||||
|
connection.
|
||||||
|
</p>
|
||||||
|
<p className="my-4">
|
||||||
|
It also means that whatever tools you build on top of the KittyCAD
|
||||||
|
Geometry Engine will be able to run on any device with a browser,
|
||||||
|
and you won't have to worry about the performance of the device.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Code Editing
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
53
src/routes/Onboarding/UserMenu.tsx
Normal file
53
src/routes/Onboarding/UserMenu.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
|
import { useStore } from '../../useStore'
|
||||||
|
|
||||||
|
export default function UserMenu() {
|
||||||
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
|
}))
|
||||||
|
const dismiss = useDismiss()
|
||||||
|
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'max-w-xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
|
||||||
|
(buttonDownInStream ? '' : ' pointer-events-auto')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<section className="flex-1">
|
||||||
|
<h2 className="text-2xl">User Menu</h2>
|
||||||
|
<p className="my-4">
|
||||||
|
Click your avatar on the upper right to open the user menu. You can
|
||||||
|
change your settings, sign out, or report a bug.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => dismiss('../../')}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Dismiss
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={next}
|
||||||
|
icon={{ icon: faArrowRight }}
|
||||||
|
>
|
||||||
|
Next: Project Menu
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,18 +1,35 @@
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { Outlet, useNavigate } from 'react-router-dom'
|
import { Outlet, useNavigate } from 'react-router-dom'
|
||||||
import Introduction from './Introduction'
|
import Introduction from './Introduction'
|
||||||
import Units from './Units'
|
|
||||||
import Camera from './Camera'
|
import Camera from './Camera'
|
||||||
import Sketching from './Sketching'
|
import Sketching from './Sketching'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
|
import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
import Streaming from './Streaming'
|
||||||
|
import CodeEditor from './CodeEditor'
|
||||||
|
import ParametricModeling from './ParametricModeling'
|
||||||
|
import InteractiveNumbers from './InteractiveNumbers'
|
||||||
|
import CmdK from './CmdK'
|
||||||
|
import UserMenu from './UserMenu'
|
||||||
|
import ProjectMenu from './ProjectMenu'
|
||||||
|
import Export from './Export'
|
||||||
|
import FutureWork from './FutureWork'
|
||||||
|
|
||||||
export const onboardingPaths = {
|
export const onboardingPaths = {
|
||||||
INDEX: '/',
|
INDEX: '/',
|
||||||
UNITS: '/units',
|
|
||||||
CAMERA: '/camera',
|
CAMERA: '/camera',
|
||||||
|
STREAMING: '/streaming',
|
||||||
|
EDITOR: '/editor',
|
||||||
|
PARAMETRIC_MODELING: '/parametric-modeling',
|
||||||
|
INTERACTIVE_NUMBERS: '/interactive-numbers',
|
||||||
|
COMMAND_K: '/command-k',
|
||||||
|
USER_MENU: '/user-menu',
|
||||||
|
PROJECT_MENU: '/project-menu',
|
||||||
|
EXPORT: '/export',
|
||||||
|
MOVE: '/move',
|
||||||
SKETCHING: '/sketching',
|
SKETCHING: '/sketching',
|
||||||
|
FUTURE_WORK: '/future-work',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onboardingRoutes = [
|
export const onboardingRoutes = [
|
||||||
@ -20,18 +37,51 @@ export const onboardingRoutes = [
|
|||||||
index: true,
|
index: true,
|
||||||
element: <Introduction />,
|
element: <Introduction />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: makeUrlPathRelative(onboardingPaths.UNITS),
|
|
||||||
element: <Units />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: makeUrlPathRelative(onboardingPaths.CAMERA),
|
path: makeUrlPathRelative(onboardingPaths.CAMERA),
|
||||||
element: <Camera />,
|
element: <Camera />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.STREAMING),
|
||||||
|
element: <Streaming />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.EDITOR),
|
||||||
|
element: <CodeEditor />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.PARAMETRIC_MODELING),
|
||||||
|
element: <ParametricModeling />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.INTERACTIVE_NUMBERS),
|
||||||
|
element: <InteractiveNumbers />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.COMMAND_K),
|
||||||
|
element: <CmdK />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.USER_MENU),
|
||||||
|
element: <UserMenu />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.PROJECT_MENU),
|
||||||
|
element: <ProjectMenu />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.EXPORT),
|
||||||
|
element: <Export />,
|
||||||
|
},
|
||||||
|
// Export / conversion API
|
||||||
{
|
{
|
||||||
path: makeUrlPathRelative(onboardingPaths.SKETCHING),
|
path: makeUrlPathRelative(onboardingPaths.SKETCHING),
|
||||||
element: <Sketching />,
|
element: <Sketching />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: makeUrlPathRelative(onboardingPaths.FUTURE_WORK),
|
||||||
|
element: <FutureWork />,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export function useNextClick(newStatus: string) {
|
export function useNextClick(newStatus: string) {
|
||||||
@ -45,7 +95,7 @@ export function useNextClick(newStatus: string) {
|
|||||||
type: 'Set Onboarding Status',
|
type: 'Set Onboarding Status',
|
||||||
data: { onboardingStatus: newStatus },
|
data: { onboardingStatus: newStatus },
|
||||||
})
|
})
|
||||||
navigate((newStatus !== onboardingPaths.UNITS ? '..' : '.') + newStatus)
|
navigate((newStatus !== onboardingPaths.CAMERA ? '..' : '.') + newStatus)
|
||||||
}, [newStatus, send, navigate])
|
}, [newStatus, send, navigate])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import { EngineCommandManager } from './lang/std/engineConnection'
|
|||||||
import { KCLError } from './lang/errors'
|
import { KCLError } from './lang/errors'
|
||||||
import { deferExecution } from 'lib/utils'
|
import { deferExecution } from 'lib/utils'
|
||||||
import { _executor } from './lang/executor'
|
import { _executor } from './lang/executor'
|
||||||
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
|
||||||
export type Selection = {
|
export type Selection = {
|
||||||
type: 'default' | 'line-end' | 'line-mid'
|
type: 'default' | 'line-end' | 'line-mid'
|
||||||
@ -409,7 +410,7 @@ export const useStore = create<StoreState>()(
|
|||||||
}, 100) as unknown as number
|
}, 100) as unknown as number
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
code: '',
|
code: bracket,
|
||||||
setCode: (code) => set({ code }),
|
setCode: (code) => set({ code }),
|
||||||
deferredSetCode: (code) => {
|
deferredSetCode: (code) => {
|
||||||
set({ code })
|
set({ code })
|
||||||
|
Reference in New Issue
Block a user