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  | 
							
								
								
									
										15
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								src/App.tsx
									
									
									
									
									
								
							@ -117,12 +117,13 @@ export function App() {
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const paneOpacity =
 | 
			
		||||
    onboardingStatus === onboardingPaths.CAMERA
 | 
			
		||||
      ? 'opacity-20'
 | 
			
		||||
      : didDragInStream
 | 
			
		||||
      ? 'opacity-40'
 | 
			
		||||
      : ''
 | 
			
		||||
  const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
 | 
			
		||||
    (p) => p === onboardingStatus
 | 
			
		||||
  )
 | 
			
		||||
    ? 'opacity-20'
 | 
			
		||||
    : didDragInStream
 | 
			
		||||
    ? 'opacity-40'
 | 
			
		||||
    : ''
 | 
			
		||||
 | 
			
		||||
  // Use file code loaded from disk
 | 
			
		||||
  // on mount, and overwrite any locally-stored code
 | 
			
		||||
@ -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',
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <div className="h-full flex flex-col justify-between">
 | 
			
		||||
        <div id="code-pane" className="h-full flex flex-col justify-between">
 | 
			
		||||
          <CollapsiblePanel
 | 
			
		||||
            title="Code"
 | 
			
		||||
            icon={faCode}
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,18 @@
 | 
			
		||||
import { useRouteError } from 'react-router-dom'
 | 
			
		||||
 | 
			
		||||
export const ErrorPage = () => {
 | 
			
		||||
  let error = useRouteError()
 | 
			
		||||
 | 
			
		||||
  console.error('error', error)
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flex flex-col items-center justify-center h-screen">
 | 
			
		||||
      <h1 className="text-4xl font-bold">404</h1>
 | 
			
		||||
      <p className="text-2xl font-bold">Page not found</p>
 | 
			
		||||
      <section className="max-w-full xl:max-w-4xl mx-auto">
 | 
			
		||||
        <h1 className="text-4xl mb-8 font-bold">
 | 
			
		||||
          An unexpected error occurred
 | 
			
		||||
        </h1>
 | 
			
		||||
        <p>{String(error)}</p>
 | 
			
		||||
      </section>
 | 
			
		||||
    </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 { onboardingPaths, useDismiss, useNextClick } from '.'
 | 
			
		||||
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() {
 | 
			
		||||
  const { buttonDownInStream } = useStore((s) => ({
 | 
			
		||||
    buttonDownInStream: s.buttonDownInStream,
 | 
			
		||||
  }))
 | 
			
		||||
  const dismiss = useDismiss()
 | 
			
		||||
  const next = useNextClick(onboardingPaths.SKETCHING)
 | 
			
		||||
  const next = useNextClick(onboardingPaths.STREAMING)
 | 
			
		||||
  const {
 | 
			
		||||
    settings: {
 | 
			
		||||
      send,
 | 
			
		||||
      state: {
 | 
			
		||||
        context: { cameraControls },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  } = useGlobalStateContext()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <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')
 | 
			
		||||
        }
 | 
			
		||||
      >
 | 
			
		||||
        <h1 className="text-2xl font-bold">Camera</h1>
 | 
			
		||||
        <p className="mt-6">
 | 
			
		||||
          Moving the camera is easy! The controls are as you might expect:
 | 
			
		||||
        </p>
 | 
			
		||||
        <ul className="list-disc list-outside ms-8 mb-4">
 | 
			
		||||
          <li>Click and drag anywhere in the scene to rotate the camera</li>
 | 
			
		||||
          <li>
 | 
			
		||||
            Hold down the <kbd>Shift</kbd> key while clicking and dragging to
 | 
			
		||||
            pan the camera
 | 
			
		||||
          </li>
 | 
			
		||||
          <li>
 | 
			
		||||
            Hold down the <kbd>Ctrl</kbd> key while dragging to zoom. You can
 | 
			
		||||
            also use the scroll wheel to zoom in and out.
 | 
			
		||||
          </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
        <p>
 | 
			
		||||
          What you're seeing here is just a video, and your interactions are
 | 
			
		||||
          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">
 | 
			
		||||
        <SettingsSection
 | 
			
		||||
          title="Camera Controls"
 | 
			
		||||
          description="How you want to control the camera in the 3D view. Try them out in the 3D view."
 | 
			
		||||
        >
 | 
			
		||||
          <select
 | 
			
		||||
            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>
 | 
			
		||||
              <strong>Pan:</strong>{' '}
 | 
			
		||||
              {cameraMouseDragGuards[cameraControls].pan.description}
 | 
			
		||||
            </li>
 | 
			
		||||
            <li>
 | 
			
		||||
              <strong>Zoom:</strong>{' '}
 | 
			
		||||
              {cameraMouseDragGuards[cameraControls].zoom.description}
 | 
			
		||||
            </li>
 | 
			
		||||
            <li>
 | 
			
		||||
              <strong>Rotate:</strong>{' '}
 | 
			
		||||
              {cameraMouseDragGuards[cameraControls].rotate.description}
 | 
			
		||||
            </li>
 | 
			
		||||
          </ul>
 | 
			
		||||
        </SettingsSection>
 | 
			
		||||
        <div className="flex justify-between">
 | 
			
		||||
          <ActionButton
 | 
			
		||||
            Element="button"
 | 
			
		||||
            onClick={() => dismiss('../../')}
 | 
			
		||||
@ -59,7 +88,7 @@ export default function Units() {
 | 
			
		||||
            onClick={next}
 | 
			
		||||
            icon={{ icon: faArrowRight }}
 | 
			
		||||
          >
 | 
			
		||||
            Next: Sketching
 | 
			
		||||
            Next: Streaming
 | 
			
		||||
          </ActionButton>
 | 
			
		||||
        </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 { ActionButton } from '../../components/ActionButton'
 | 
			
		||||
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 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 (
 | 
			
		||||
    <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">
 | 
			
		||||
          Welcome to the KittyCAD Modeling App
 | 
			
		||||
        {!isTauri() ? (
 | 
			
		||||
          <>
 | 
			
		||||
            <h1 className="text-2xl font-bold text-warn-80 dark:text-warn-10">
 | 
			
		||||
              Replaying onboarding resets your code
 | 
			
		||||
            </h1>
 | 
			
		||||
            <p className="my-4">
 | 
			
		||||
              We see you have some of your own code written in this project.
 | 
			
		||||
              Please save it somewhere else before continuing the onboarding.
 | 
			
		||||
            </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={() => {
 | 
			
		||||
                  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>
 | 
			
		||||
            </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>
 | 
			
		||||
        <p className="my-2">
 | 
			
		||||
          A browser-first, GPU-streaming hardware design tool that lets you edit
 | 
			
		||||
          visually, with code, or both.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p className="my-2">
 | 
			
		||||
          Powered by the first API created for anyone to build hardware design
 | 
			
		||||
          tools.
 | 
			
		||||
        </p>
 | 
			
		||||
        <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">
 | 
			
		||||
          <ActionButton
 | 
			
		||||
            Element="button"
 | 
			
		||||
@ -44,5 +194,7 @@ export default function Introduction() {
 | 
			
		||||
        </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 { ActionButton } from '../../components/ActionButton'
 | 
			
		||||
import { useDismiss } from '.'
 | 
			
		||||
import { onboardingPaths, useDismiss, useNextClick } from '.'
 | 
			
		||||
import { useStore } from 'useStore'
 | 
			
		||||
import { useEffect } from 'react'
 | 
			
		||||
 | 
			
		||||
export default function Sketching() {
 | 
			
		||||
  const { setCode, buttonDownInStream } = useStore((s) => ({
 | 
			
		||||
    setCode: s.setCode,
 | 
			
		||||
    buttonDownInStream: s.buttonDownInStream,
 | 
			
		||||
  }))
 | 
			
		||||
  const dismiss = useDismiss()
 | 
			
		||||
  const next = useNextClick(onboardingPaths.FUTURE_WORK)
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setCode('')
 | 
			
		||||
  }, [setCode])
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="fixed grid justify-center items-end inset-0 bg-chalkboard-110/50 z-50">
 | 
			
		||||
      <div className="max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
 | 
			
		||||
    <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')
 | 
			
		||||
        }
 | 
			
		||||
      >
 | 
			
		||||
        <h1 className="text-2xl font-bold">Sketching</h1>
 | 
			
		||||
        <p className="mt-6">
 | 
			
		||||
          We still have to implement this step, and the rest of the tutorial!
 | 
			
		||||
        <p className="my-4">
 | 
			
		||||
          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>
 | 
			
		||||
        <div className="flex justify-between mt-6">
 | 
			
		||||
          <ActionButton
 | 
			
		||||
@ -28,10 +51,10 @@ export default function Sketching() {
 | 
			
		||||
          </ActionButton>
 | 
			
		||||
          <ActionButton
 | 
			
		||||
            Element="button"
 | 
			
		||||
            onClick={() => dismiss('../../')}
 | 
			
		||||
            onClick={next}
 | 
			
		||||
            icon={{ icon: faArrowRight }}
 | 
			
		||||
          >
 | 
			
		||||
            Finish
 | 
			
		||||
            Next: Future Work
 | 
			
		||||
          </ActionButton>
 | 
			
		||||
        </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 { Outlet, useNavigate } from 'react-router-dom'
 | 
			
		||||
import Introduction from './Introduction'
 | 
			
		||||
import Units from './Units'
 | 
			
		||||
import Camera from './Camera'
 | 
			
		||||
import Sketching from './Sketching'
 | 
			
		||||
import { useCallback } from 'react'
 | 
			
		||||
import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
 | 
			
		||||
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 = {
 | 
			
		||||
  INDEX: '/',
 | 
			
		||||
  UNITS: '/units',
 | 
			
		||||
  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',
 | 
			
		||||
  FUTURE_WORK: '/future-work',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const onboardingRoutes = [
 | 
			
		||||
@ -20,18 +37,51 @@ export const onboardingRoutes = [
 | 
			
		||||
    index: true,
 | 
			
		||||
    element: <Introduction />,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: makeUrlPathRelative(onboardingPaths.UNITS),
 | 
			
		||||
    element: <Units />,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: makeUrlPathRelative(onboardingPaths.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),
 | 
			
		||||
    element: <Sketching />,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: makeUrlPathRelative(onboardingPaths.FUTURE_WORK),
 | 
			
		||||
    element: <FutureWork />,
 | 
			
		||||
  },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export function useNextClick(newStatus: string) {
 | 
			
		||||
@ -45,7 +95,7 @@ export function useNextClick(newStatus: string) {
 | 
			
		||||
      type: 'Set Onboarding Status',
 | 
			
		||||
      data: { onboardingStatus: newStatus },
 | 
			
		||||
    })
 | 
			
		||||
    navigate((newStatus !== onboardingPaths.UNITS ? '..' : '.') + newStatus)
 | 
			
		||||
    navigate((newStatus !== onboardingPaths.CAMERA ? '..' : '.') + newStatus)
 | 
			
		||||
  }, [newStatus, send, navigate])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ import { EngineCommandManager } from './lang/std/engineConnection'
 | 
			
		||||
import { KCLError } from './lang/errors'
 | 
			
		||||
import { deferExecution } from 'lib/utils'
 | 
			
		||||
import { _executor } from './lang/executor'
 | 
			
		||||
import { bracket } from 'lib/exampleKcl'
 | 
			
		||||
 | 
			
		||||
export type Selection = {
 | 
			
		||||
  type: 'default' | 'line-end' | 'line-mid'
 | 
			
		||||
@ -409,7 +410,7 @@ export const useStore = create<StoreState>()(
 | 
			
		||||
            }, 100) as unknown as number
 | 
			
		||||
          )
 | 
			
		||||
        },
 | 
			
		||||
        code: '',
 | 
			
		||||
        code: bracket,
 | 
			
		||||
        setCode: (code) => set({ code }),
 | 
			
		||||
        deferredSetCode: (code) => {
 | 
			
		||||
          set({ code })
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user