Add settings UI page (#171)
* Add theme colors from Figma * Rough-in of AppHeader * Add styled ActionButton * Add react-router and placeholder Settings page * Add ability to set persistent defaultDir * Add react-hot-toast for save success message * Add defaultProjectName setting * Handle case of stale empty defaultDir in storage * Wrap app in BrowserRouter * Wrap test App in BrowserRouter * Don't need BrowserRouter outside of testing because we use RouterProvider
This commit is contained in:
		@ -4,6 +4,9 @@
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@codemirror/lang-javascript": "^6.1.1",
 | 
			
		||||
    "@fortawesome/fontawesome-svg-core": "^6.4.0",
 | 
			
		||||
    "@fortawesome/free-solid-svg-icons": "^6.4.0",
 | 
			
		||||
    "@fortawesome/react-fontawesome": "^0.2.0",
 | 
			
		||||
    "@headlessui/react": "^1.7.13",
 | 
			
		||||
    "@tauri-apps/api": "^1.3.0",
 | 
			
		||||
    "@testing-library/jest-dom": "^5.14.1",
 | 
			
		||||
@ -19,8 +22,10 @@
 | 
			
		||||
    "http-server": "^14.1.1",
 | 
			
		||||
    "react": "^18.2.0",
 | 
			
		||||
    "react-dom": "^18.2.0",
 | 
			
		||||
    "react-hot-toast": "^2.4.1",
 | 
			
		||||
    "react-json-view": "^1.21.3",
 | 
			
		||||
    "react-modal-promise": "^1.0.2",
 | 
			
		||||
    "react-router-dom": "^6.14.1",
 | 
			
		||||
    "react-scripts": "5.0.1",
 | 
			
		||||
    "sketch-helpers": "^0.0.3",
 | 
			
		||||
    "swr": "^2.0.4",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										37
									
								
								public/kitt-8bit-winking.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								public/kitt-8bit-winking.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
<svg width="25" height="34" viewBox="0 0 25 34" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
<path d="M3 31V30H1V29H0V8H1V7H2V6H3V5H4V4H21V5H22V6H23V7H24V8H25V29H24V30H22V31H19V32H20V34H14V32H15V31H10V32H11V34H5V32H6V31H3Z" fill="#101412"/>
 | 
			
		||||
<path d="M6 31V29.5H10V31H9V32H10V33H6V32H7V31H6Z" fill="#4B4862"/>
 | 
			
		||||
<path d="M15 31V29.5H19V31H18V32H19V33H15V32H16V31H15Z" fill="#4B4862"/>
 | 
			
		||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 24.5V29H3V30H22V29H24V24.5H1ZM9 29V27H16V29H9Z" fill="#9BADB7"/>
 | 
			
		||||
<path d="M1 25V23H24V25H1Z" fill="#BECAD0"/>
 | 
			
		||||
<path d="M2 27V26H7V27H2Z" fill="#2B3E48"/>
 | 
			
		||||
<path d="M4 29V28H7V29H4Z" fill="#2B3E48"/>
 | 
			
		||||
<path d="M18 27V26H19V27H18Z" fill="#2B3E48"/>
 | 
			
		||||
<path d="M20 27V26H21V27H20Z" fill="#2B3E48"/>
 | 
			
		||||
<path d="M22 27V26H23V27H22Z" fill="#2B3E48"/>
 | 
			
		||||
<path d="M18 29V28H21V29H18Z" fill="#2B3E48"/>
 | 
			
		||||
<path d="M22 7V6H21V5H4V6H3V7H2V8H1V10H24V8H23V7H22Z" fill="#FBF580"/>
 | 
			
		||||
<path d="M1 24V22H24V24H1Z" fill="#AEAA4C"/>
 | 
			
		||||
<path d="M24 9H1V23H24V9Z" fill="#E5E3A1"/>
 | 
			
		||||
<path d="M4 12V11H21V12H22V20H21V21H4V20H3V12H4Z" fill="#1F2320"/>
 | 
			
		||||
<rect x="10" y="5" width="5" height="2" fill="#AEAA4C"/>
 | 
			
		||||
<path d="M16 13V12H18V16H17L16 13Z" fill="#DBFF3C"/>
 | 
			
		||||
<path d="M11 16H14V17H13V19H16V18H17V19H16V20H9V19H8V18H9V19H12V17H11V16Z" fill="#DBFF3C"/>
 | 
			
		||||
<path d="M9 15V14H6V15H5V14H6V13H9V14H10V15H9Z" fill="#DBFF3C"/>
 | 
			
		||||
<path d="M4 7V6H5V4H6V2H7V1H8V2H9V4H10V6H11V7H4Z" fill="#DBFF3C"/>
 | 
			
		||||
<path d="M21 6V7H14V6H15V4H16V2H17V1H18V2H19V4H20V6H21Z" fill="#DBFF3C"/>
 | 
			
		||||
<path d="M16 2V0H19V2H20V4H21V5.5H20V4H19V2H18V1H17V2H16V4H15V5.5H14V4H15V2H16Z" fill="#92C51B"/>
 | 
			
		||||
<path d="M6 2V0H9V2H10V4H11V5.5H10V4H9V2H8V1H7V2H6V4H5V5.5H4V4H5V2H6Z" fill="#92C51B"/>
 | 
			
		||||
<rect x="11" y="6" width="3" height="1" fill="#D0CC6A"/>
 | 
			
		||||
<path d="M16 7V6H17V5H18V6H19V7H16Z" fill="#76AA1D"/>
 | 
			
		||||
<path d="M7 6V5H8V6H9V7H6V6H7Z" fill="#76AA1D"/>
 | 
			
		||||
<path d="M21 7V6H20V5H21V6H22V7H21Z" fill="#76AA1D"/>
 | 
			
		||||
<path d="M4 6V7H3V6H4V5H5V6H4Z" fill="#76AA1D"/>
 | 
			
		||||
<path d="M10 5H11V6H12V7H11V6H10V5Z" fill="#76AA1D"/>
 | 
			
		||||
<path d="M14 5H15V6H14V7H13V6H14V5Z" fill="#76AA1D"/>
 | 
			
		||||
<path d="M17 13H16V16H17V13Z" fill="#92C51B"/>
 | 
			
		||||
<path d="M2 25V23H1V25H2Z" fill="#D0CC6A"/>
 | 
			
		||||
<path d="M23 25V23H24V25H23Z" fill="#D0CC6A"/>
 | 
			
		||||
<path d="M4 24V23H7V24H4Z" fill="#D56161"/>
 | 
			
		||||
<path d="M4 25V24H7V25H4Z" fill="#AC3232"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 2.2 KiB  | 
							
								
								
									
										18
									
								
								public/kitt-arcade-winking.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								public/kitt-arcade-winking.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
<svg width="25" height="34" viewBox="0 0 25 34" fill="none" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
<path d="M3 31V30H1V29H0V8H1V7H2V6H3V5H4V3H5V2H6V1H7V0H8V1H9V2H10V3H11V4H14V3H15V2H16V1H17V0H18V1H19V2H20V3H21V5H22V6H23V7H24V8H25V29H24V30H22V31H19V32H20V34H14V32H15V31H10V32H11V34H5V32H6V31H3Z" fill="#101412"/>
 | 
			
		||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 5H14V4H15V3H16V2H17V1H18V2H19V3H20V5H21V6H22V7H23V8H24V9V10V23V24V25V29H22V30H18V31V32H19V33H15V32H16V31V30H9V31V32H10V33H6V32H7V31V30H3V29H1V25V24V23V10V9V8H2V7H3V6H4V5H5V3H6V2H7V1H8V2H9V3H10V4H11V5ZM2 26V27H7V26H2ZM4 28V29H7V28H4ZM18 27V26H19V27H18ZM20 26V27H21V26H20ZM22 27V26H23V27H22ZM18 28V29H21V28H18ZM9 27H16V29H9V27Z" fill="#D0FF00"/>
 | 
			
		||||
<path d="M1 24V23H24V24H1Z" fill="#B1E515"/>
 | 
			
		||||
<path d="M4 12V11H21V12H22V20H21V21H4V20H3V12H4Z" fill="#1F2320"/>
 | 
			
		||||
<path d="M16 16V12H18V16H16Z" fill="#D0FF00"/>
 | 
			
		||||
<path d="M11 16H14V17H13V19H16V18H17V19H16V20H9V19H8V18H9V19H12V17H11V16Z" fill="#D0FF00"/>
 | 
			
		||||
<path d="M9 15V14H6V15H5V14H6V13H9V14H10V15H9Z" fill="#D0FF00"/>
 | 
			
		||||
<path d="M22 8V7H23V8H22Z" fill="#B1E515"/>
 | 
			
		||||
<path d="M3 8V7H2V8H3Z" fill="#B1E515"/>
 | 
			
		||||
<path d="M21 7V6H22V7H21Z" fill="#92C51B"/>
 | 
			
		||||
<path d="M4 7V6H3V7H4Z" fill="#92C51B"/>
 | 
			
		||||
<rect x="12" y="5" width="1" height="2" fill="#B1E515"/>
 | 
			
		||||
<path d="M16 7V6H17V5H18V6H19V7H16Z" fill="#101412"/>
 | 
			
		||||
<path d="M7 6V5H8V6H9V7H6V6H7Z" fill="#101412"/>
 | 
			
		||||
<path d="M4 24V23H7V24H4Z" fill="#92C51B"/>
 | 
			
		||||
<rect x="11" y="5" width="3" height="1" fill="#92C51B"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
@ -1,5 +1,6 @@
 | 
			
		||||
import { render, screen } from '@testing-library/react'
 | 
			
		||||
import { App } from './App'
 | 
			
		||||
import { BrowserRouter } from 'react-router-dom';
 | 
			
		||||
 | 
			
		||||
let listener: ((rect: any) => void) | undefined = undefined
 | 
			
		||||
;(global as any).ResizeObserver = class ResizeObserver {
 | 
			
		||||
@ -12,7 +13,9 @@ let listener: ((rect: any) => void) | undefined = undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test('renders learn react link', () => {
 | 
			
		||||
  render(<App />)
 | 
			
		||||
  render(<BrowserRouter>
 | 
			
		||||
    <App />
 | 
			
		||||
  </BrowserRouter>)
 | 
			
		||||
  const linkElement = screen.getByText(/Variables/i)
 | 
			
		||||
  expect(linkElement).toBeInTheDocument()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ import ModalContainer from 'react-modal-promise'
 | 
			
		||||
import { EngineCommandManager } from './lang/std/engineConnection'
 | 
			
		||||
import { isOverlap } from './lib/utils'
 | 
			
		||||
import { SetToken } from './components/TokenInput'
 | 
			
		||||
import { AppHeader } from './components/AppHeader'
 | 
			
		||||
 | 
			
		||||
export function App() {
 | 
			
		||||
  const cam = useRef()
 | 
			
		||||
@ -253,6 +254,7 @@ export function App() {
 | 
			
		||||
  }, [code, isStreamReady])
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="h-screen">
 | 
			
		||||
      <AppHeader />
 | 
			
		||||
      <ModalContainer />
 | 
			
		||||
      <Allotment snap={true}>
 | 
			
		||||
 | 
			
		||||
@ -285,9 +287,6 @@ export function App() {
 | 
			
		||||
          <Logs />
 | 
			
		||||
        </Allotment>
 | 
			
		||||
        <Allotment vertical defaultSizes={[40, 400]} minSize={20}>
 | 
			
		||||
          <div>
 | 
			
		||||
            <Toolbar />
 | 
			
		||||
          </div>
 | 
			
		||||
          <Stream />
 | 
			
		||||
        </Allotment>
 | 
			
		||||
      </Allotment>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								src/Auth.tsx
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								src/Auth.tsx
									
									
									
									
									
								
							@ -4,6 +4,24 @@ import withBaseUrl from './lib/withBaseURL'
 | 
			
		||||
import { App } from './App'
 | 
			
		||||
import { SetToken } from './components/TokenInput'
 | 
			
		||||
import { useStore } from './useStore'
 | 
			
		||||
import {
 | 
			
		||||
  createBrowserRouter,
 | 
			
		||||
  RouterProvider,
 | 
			
		||||
} from "react-router-dom"
 | 
			
		||||
import { ErrorPage } from './components/ErrorPage'
 | 
			
		||||
import { Settings } from './routes/Settings'
 | 
			
		||||
 | 
			
		||||
const router = createBrowserRouter([
 | 
			
		||||
  {
 | 
			
		||||
    path: "/",
 | 
			
		||||
    element: <App />,
 | 
			
		||||
    errorElement: <ErrorPage />,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/settings",
 | 
			
		||||
    element: <Settings />,
 | 
			
		||||
  }
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
export const Auth = () => {
 | 
			
		||||
  const { data: user } = useSWR(withBaseUrl('/user'), fetcher) as any
 | 
			
		||||
@ -37,5 +55,5 @@ export const Auth = () => {
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return <App />
 | 
			
		||||
  return <RouterProvider router={router} />
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										263
									
								
								src/colors.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								src/colors.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,263 @@
 | 
			
		||||
:root {
 | 
			
		||||
    /*
 | 
			
		||||
        Generated using Catmosphere Theme Builder
 | 
			
		||||
        by KittyCAD
 | 
			
		||||
        https://catmosphere-theme-builder.vercel.app/?colors=%5B%7B%22from%22:%7B%22l%22:1,%22c%22:0.01,%22h%22:78%7D,%22to%22:%7B%22l%22:0.065,%22c%22:0.05,%22h%22:182.6%7D,%22stops%22:10,%22steps%22:12%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.45,%22h%22:122.4%7D,%22to%22:%7B%22l%22:0.13,%22c%22:0.031,%22h%22:137.2%7D,%22stops%22:10,%22steps%22:12%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.13,%22h%22:176%7D,%22to%22:%7B%22l%22:0.116,%22c%22:0.097,%22h%22:213.1%7D,%22stops%22:10,%22steps%22:12%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.169,%22h%22:144.4%7D,%22to%22:%7B%22l%22:0.12,%22c%22:0.45,%22h%22:132.7%7D,%22steps%22:12%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.087,%22h%22:261.6%7D,%22to%22:%7B%22l%22:0.22,%22c%22:0.084,%22h%22:275.5%7D,%22steps%22:12,%22uuid%22:%227tpx9pf1zd6%22%7D,%7B%22from%22:%7B%22l%22:0.954,%22c%22:0.108,%22h%22:280.6%7D,%22to%22:%7B%22l%22:0.166,%22c%22:0.188,%22h%22:263.8%7D,%22steps%22:12,%22uuid%22:%22vu652mebd3%22%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.115,%22h%22:0%7D,%22to%22:%7B%22l%22:0.096,%22c%22:0.261,%22h%22:302%7D,%22steps%22:12%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.185,%22h%22:19.8%7D,%22to%22:%7B%22l%22:0.368,%22c%22:0.45,%22h%22:9.4%7D,%22steps%22:8,%22uuid%22:%22g05inkd34l%22%7D,%7B%22from%22:%7B%22l%22:0.912,%22c%22:0.139,%22h%22:87%7D,%22to%22:%7B%22l%22:0.502,%22c%22:0.45,%22h%22:97.7%7D,%22steps%22:8,%22uuid%22:%22l892hcw4ef%22%7D,%7B%22from%22:%7B%22l%22:0.89,%22c%22:0.16,%22h%22:143.4%7D,%22to%22:%7B%22l%22:0.466,%22c%22:0.208,%22h%22:147.7%7D,%22steps%22:8,%22uuid%22:%22hkd09y9ov4h%22%7D%5D
 | 
			
		||||
    */
 | 
			
		||||
    /* Chalkboard */
 | 
			
		||||
    --chalkboard-10: oklch(99.70% 0.008766 102.8deg);
 | 
			
		||||
    --chalkboard-20: oklch(91.34% 0.009353 109.0deg);
 | 
			
		||||
    --chalkboard-30: oklch(82.99% 0.009940 115.2deg);
 | 
			
		||||
    --chalkboard-40: oklch(74.63% 0.01053 121.4deg);
 | 
			
		||||
    --chalkboard-50: oklch(66.27% 0.01111 127.6deg);
 | 
			
		||||
    --chalkboard-60: oklch(57.92% 0.01170 133.9deg);
 | 
			
		||||
    --chalkboard-70: oklch(49.56% 0.01229 140.1deg);
 | 
			
		||||
    --chalkboard-80: oklch(41.21% 0.01288 146.3deg);
 | 
			
		||||
    --chalkboard-90: oklch(32.85% 0.01346 152.5deg);
 | 
			
		||||
    --chalkboard-100: oklch(24.49% 0.01405 158.7deg);
 | 
			
		||||
    --chalkboard-110: oklch(16.14% 0.01464 164.9deg);
 | 
			
		||||
    --chalkboard-120: oklch(7.783% 0.01522 171.1deg);
 | 
			
		||||
 | 
			
		||||
    /* Energy  */
 | 
			
		||||
    --energy-10: oklch(93.31% 0.2270 122.3deg);
 | 
			
		||||
    --energy-20: oklch(86.01% 0.2092 123.6deg);
 | 
			
		||||
    --energy-30: oklch(78.71% 0.1914 125.0deg);
 | 
			
		||||
    --energy-40: oklch(71.41% 0.1736 126.3deg);
 | 
			
		||||
    --energy-50: oklch(64.10% 0.1557 127.7deg);
 | 
			
		||||
    --energy-60: oklch(56.80% 0.1379 129.1deg);
 | 
			
		||||
    --energy-70: oklch(49.50% 0.1201 130.4deg);
 | 
			
		||||
    --energy-80: oklch(42.20% 0.1023 131.8deg);
 | 
			
		||||
    --energy-90: oklch(34.90% 0.08446 133.1deg);
 | 
			
		||||
    --energy-100: oklch(27.60% 0.06664 134.5deg);
 | 
			
		||||
    --energy-110: oklch(20.30% 0.04882 135.8deg);
 | 
			
		||||
    --energy-120: oklch(13.00% 0.03100 137.2deg);
 | 
			
		||||
 | 
			
		||||
    /* Liquid */
 | 
			
		||||
    --liquid-10: oklch(93.45% 0.1002 193.1deg);
 | 
			
		||||
    --liquid-20: oklch(86.21% 0.09511 198.7deg);
 | 
			
		||||
    --liquid-30: oklch(78.97% 0.09003 204.2deg);
 | 
			
		||||
    --liquid-40: oklch(71.74% 0.08495 209.8deg);
 | 
			
		||||
    --liquid-50: oklch(64.50% 0.07988 215.3deg);
 | 
			
		||||
    --liquid-60: oklch(57.26% 0.07480 220.9deg);
 | 
			
		||||
    --liquid-70: oklch(50.03% 0.06972 226.4deg);
 | 
			
		||||
    --liquid-80: oklch(42.79% 0.06465 232.0deg);
 | 
			
		||||
    --liquid-90: oklch(35.56% 0.05957 237.5deg);
 | 
			
		||||
    --liquid-100: oklch(28.32% 0.05450 243.1deg);
 | 
			
		||||
    --liquid-110: oklch(21.08% 0.04942 248.6deg);
 | 
			
		||||
    --liquid-120: oklch(13.85% 0.04434 254.2deg);
 | 
			
		||||
 | 
			
		||||
    /* Fern */
 | 
			
		||||
    --fern-10: oklch(93.22% 0.1243 144.8deg);
 | 
			
		||||
    --fern-20: oklch(86.59% 0.1193 144.6deg);
 | 
			
		||||
    --fern-30: oklch(79.97% 0.1143 144.4deg);
 | 
			
		||||
    --fern-40: oklch(73.34% 0.1093 144.2deg);
 | 
			
		||||
    --fern-50: oklch(66.71% 0.1043 144.0deg);
 | 
			
		||||
    --fern-60: oklch(60.09% 0.09927 143.8deg);
 | 
			
		||||
    --fern-70: oklch(53.46% 0.09425 143.6deg);
 | 
			
		||||
    --fern-80: oklch(46.83% 0.08924 143.3deg);
 | 
			
		||||
    --fern-90: oklch(40.21% 0.08422 143.1deg);
 | 
			
		||||
    --fern-100: oklch(33.58% 0.07920 142.9deg);
 | 
			
		||||
    --fern-110: oklch(26.95% 0.07419 142.7deg);
 | 
			
		||||
    --fern-120: oklch(20.33% 0.06917 142.5deg);
 | 
			
		||||
 | 
			
		||||
    /* Cool */
 | 
			
		||||
    --cool-10: oklch(97.71% 0.03321 196.6deg);
 | 
			
		||||
    --cool-20: oklch(90.82% 0.03783 203.8deg);
 | 
			
		||||
    --cool-30: oklch(83.94% 0.04245 211.0deg);
 | 
			
		||||
    --cool-40: oklch(77.06% 0.04706 218.1deg);
 | 
			
		||||
    --cool-50: oklch(70.18% 0.05168 225.3deg);
 | 
			
		||||
    --cool-60: oklch(63.29% 0.05630 232.5deg);
 | 
			
		||||
    --cool-70: oklch(56.41% 0.06091 239.6deg);
 | 
			
		||||
    --cool-80: oklch(49.53% 0.06553 246.8deg);
 | 
			
		||||
    --cool-90: oklch(42.65% 0.07015 254.0deg);
 | 
			
		||||
    --cool-100: oklch(35.76% 0.07477 261.2deg);
 | 
			
		||||
    --cool-110: oklch(28.88% 0.07938 268.3deg);
 | 
			
		||||
    --cool-120: oklch(22.00% 0.08400 275.5deg);
 | 
			
		||||
 | 
			
		||||
    /* River */
 | 
			
		||||
    --river-10: oklch(93.35% 0.03169 273.4deg);
 | 
			
		||||
    --river-20: oklch(86.91% 0.04221 273.1deg);
 | 
			
		||||
    --river-30: oklch(80.46% 0.05274 272.7deg);
 | 
			
		||||
    --river-40: oklch(74.01% 0.06326 272.4deg);
 | 
			
		||||
    --river-50: oklch(67.57% 0.07378 272.0deg);
 | 
			
		||||
    --river-60: oklch(61.12% 0.08430 271.7deg);
 | 
			
		||||
    --river-70: oklch(54.67% 0.09483 271.4deg);
 | 
			
		||||
    --river-80: oklch(48.22% 0.1053 271.0deg);
 | 
			
		||||
    --river-90: oklch(41.78% 0.1159 270.7deg);
 | 
			
		||||
    --river-100: oklch(35.33% 0.1264 270.4deg);
 | 
			
		||||
    --river-110: oklch(28.88% 0.1369 270.0deg);
 | 
			
		||||
    --river-120: oklch(22.44% 0.1474 269.7deg);
 | 
			
		||||
 | 
			
		||||
    /* Berry */
 | 
			
		||||
    --berry-10: oklch(93.77% 0.05212 329.0deg);
 | 
			
		||||
    --berry-20: oklch(87.30% 0.05912 325.3deg);
 | 
			
		||||
    --berry-30: oklch(80.82% 0.06612 321.6deg);
 | 
			
		||||
    --berry-40: oklch(74.34% 0.07313 317.8deg);
 | 
			
		||||
    --berry-50: oklch(67.86% 0.08013 314.1deg);
 | 
			
		||||
    --berry-60: oklch(61.39% 0.08713 310.3deg);
 | 
			
		||||
    --berry-70: oklch(54.91% 0.09413 306.6deg);
 | 
			
		||||
    --berry-80: oklch(48.43% 0.1011 302.8deg);
 | 
			
		||||
    --berry-90: oklch(41.95% 0.1081 299.1deg);
 | 
			
		||||
    --berry-100: oklch(35.47% 0.1151 295.4deg);
 | 
			
		||||
    --berry-110: oklch(29.00% 0.1221 291.6deg);
 | 
			
		||||
    --berry-120: oklch(22.52% 0.1291 287.9deg);
 | 
			
		||||
 | 
			
		||||
    /* Destroy */
 | 
			
		||||
    --destroy-10: oklch(88.21% 0.06281 14.85deg);
 | 
			
		||||
    --destroy-20: oklch(83.23% 0.08511 16.91deg);
 | 
			
		||||
    --destroy-30: oklch(78.25% 0.1074 18.96deg);
 | 
			
		||||
    --destroy-40: oklch(73.27% 0.1297 21.01deg);
 | 
			
		||||
    --destroy-50: oklch(68.29% 0.1520 23.07deg);
 | 
			
		||||
    --destroy-60: oklch(63.31% 0.1743 25.12deg);
 | 
			
		||||
    --destroy-70: oklch(58.33% 0.1966 27.18deg);
 | 
			
		||||
    --destroy-80: oklch(53.35% 0.2189 29.23deg);
 | 
			
		||||
 | 
			
		||||
    /* Warn */
 | 
			
		||||
    --warn-10: oklch(90.19% 0.1361 92.00deg);
 | 
			
		||||
    --warn-20: oklch(84.60% 0.1388 84.84deg);
 | 
			
		||||
    --warn-30: oklch(79.01% 0.1414 77.68deg);
 | 
			
		||||
    --warn-40: oklch(73.42% 0.1440 70.52deg);
 | 
			
		||||
    --warn-50: oklch(67.83% 0.1466 63.36deg);
 | 
			
		||||
    --warn-60: oklch(62.24% 0.1492 56.20deg);
 | 
			
		||||
    --warn-70: oklch(56.65% 0.1518 49.04deg);
 | 
			
		||||
    --warn-80: oklch(51.06% 0.1544 41.88deg);
 | 
			
		||||
 | 
			
		||||
    /* Succeed */
 | 
			
		||||
    --succeed-10: oklch(89.00% 0.1600 143.4deg);
 | 
			
		||||
    --succeed-20: oklch(83.23% 0.1608 143.3deg);
 | 
			
		||||
    --succeed-30: oklch(77.46% 0.1616 143.1deg);
 | 
			
		||||
    --succeed-40: oklch(71.69% 0.1623 143.0deg);
 | 
			
		||||
    --succeed-50: oklch(65.92% 0.1631 142.9deg);
 | 
			
		||||
    --succeed-60: oklch(60.16% 0.1639 142.8deg);
 | 
			
		||||
    --succeed-70: oklch(54.39% 0.1647 142.6deg);
 | 
			
		||||
    --succeed-80: oklch(48.62% 0.1654 142.5deg);
 | 
			
		||||
 | 
			
		||||
    /* Base values for use with Tailwind. */
 | 
			
		||||
    /* Chalkboard */
 | 
			
		||||
    --_chalkboard-10: 99.70% 0.008766 102.8deg;
 | 
			
		||||
    --_chalkboard-20: 91.34% 0.009353 109.0deg;
 | 
			
		||||
    --_chalkboard-30: 82.99% 0.009940 115.2deg;
 | 
			
		||||
    --_chalkboard-40: 74.63% 0.01053 121.4deg;
 | 
			
		||||
    --_chalkboard-50: 66.27% 0.01111 127.6deg;
 | 
			
		||||
    --_chalkboard-60: 57.92% 0.01170 133.9deg;
 | 
			
		||||
    --_chalkboard-70: 49.56% 0.01229 140.1deg;
 | 
			
		||||
    --_chalkboard-80: 41.21% 0.01288 146.3deg;
 | 
			
		||||
    --_chalkboard-90: 32.85% 0.01346 152.5deg;
 | 
			
		||||
    --_chalkboard-100: 24.49% 0.01405 158.7deg;
 | 
			
		||||
    --_chalkboard-110: 16.14% 0.01464 164.9deg;
 | 
			
		||||
    --_chalkboard-120: 7.783% 0.01522 171.1deg;
 | 
			
		||||
 | 
			
		||||
    /* Energy  */
 | 
			
		||||
    --_energy-10: 93.31% 0.2270 122.3deg;
 | 
			
		||||
    --_energy-20: 86.01% 0.2092 123.6deg;
 | 
			
		||||
    --_energy-30: 78.71% 0.1914 125.0deg;
 | 
			
		||||
    --_energy-40: 71.41% 0.1736 126.3deg;
 | 
			
		||||
    --_energy-50: 64.10% 0.1557 127.7deg;
 | 
			
		||||
    --_energy-60: 56.80% 0.1379 129.1deg;
 | 
			
		||||
    --_energy-70: 49.50% 0.1201 130.4deg;
 | 
			
		||||
    --_energy-80: 42.20% 0.1023 131.8deg;
 | 
			
		||||
    --_energy-90: 34.90% 0.08446 133.1deg;
 | 
			
		||||
    --_energy-100: 27.60% 0.06664 134.5deg;
 | 
			
		||||
    --_energy-110: 20.30% 0.04882 135.8deg;
 | 
			
		||||
    --_energy-120: 13.00% 0.03100 137.2deg;
 | 
			
		||||
 | 
			
		||||
    /* Liquid */
 | 
			
		||||
    --_liquid-10: 93.45% 0.1002 193.1deg;
 | 
			
		||||
    --_liquid-20: 86.21% 0.09511 198.7deg;
 | 
			
		||||
    --_liquid-30: 78.97% 0.09003 204.2deg;
 | 
			
		||||
    --_liquid-40: 71.74% 0.08495 209.8deg;
 | 
			
		||||
    --_liquid-50: 64.50% 0.07988 215.3deg;
 | 
			
		||||
    --_liquid-60: 57.26% 0.07480 220.9deg;
 | 
			
		||||
    --_liquid-70: 50.03% 0.06972 226.4deg;
 | 
			
		||||
    --_liquid-80: 42.79% 0.06465 232.0deg;
 | 
			
		||||
    --_liquid-90: 35.56% 0.05957 237.5deg;
 | 
			
		||||
    --_liquid-100: 28.32% 0.05450 243.1deg;
 | 
			
		||||
    --_liquid-110: 21.08% 0.04942 248.6deg;
 | 
			
		||||
    --_liquid-120: 13.85% 0.04434 254.2deg;
 | 
			
		||||
 | 
			
		||||
    /* Fern */
 | 
			
		||||
    --_fern-10: 93.22% 0.1243 144.8deg;
 | 
			
		||||
    --_fern-20: 86.59% 0.1193 144.6deg;
 | 
			
		||||
    --_fern-30: 79.97% 0.1143 144.4deg;
 | 
			
		||||
    --_fern-40: 73.34% 0.1093 144.2deg;
 | 
			
		||||
    --_fern-50: 66.71% 0.1043 144.0deg;
 | 
			
		||||
    --_fern-60: 60.09% 0.09927 143.8deg;
 | 
			
		||||
    --_fern-70: 53.46% 0.09425 143.6deg;
 | 
			
		||||
    --_fern-80: 46.83% 0.08924 143.3deg;
 | 
			
		||||
    --_fern-90: 40.21% 0.08422 143.1deg;
 | 
			
		||||
    --_fern-100: 33.58% 0.07920 142.9deg;
 | 
			
		||||
    --_fern-110: 26.95% 0.07419 142.7deg;
 | 
			
		||||
    --_fern-120: 20.33% 0.06917 142.5deg;
 | 
			
		||||
 | 
			
		||||
    /* Cool */
 | 
			
		||||
    --_cool-10: 97.71% 0.03321 196.6deg;
 | 
			
		||||
    --_cool-20: 90.82% 0.03783 203.8deg;
 | 
			
		||||
    --_cool-30: 83.94% 0.04245 211.0deg;
 | 
			
		||||
    --_cool-40: 77.06% 0.04706 218.1deg;
 | 
			
		||||
    --_cool-50: 70.18% 0.05168 225.3deg;
 | 
			
		||||
    --_cool-60: 63.29% 0.05630 232.5deg;
 | 
			
		||||
    --_cool-70: 56.41% 0.06091 239.6deg;
 | 
			
		||||
    --_cool-80: 49.53% 0.06553 246.8deg;
 | 
			
		||||
    --_cool-90: 42.65% 0.07015 254.0deg;
 | 
			
		||||
    --_cool-100: 35.76% 0.07477 261.2deg;
 | 
			
		||||
    --_cool-110: 28.88% 0.07938 268.3deg;
 | 
			
		||||
    --_cool-120: 22.00% 0.08400 275.5deg;
 | 
			
		||||
 | 
			
		||||
    /* River */
 | 
			
		||||
    --_river-10: 93.35% 0.03169 273.4deg;
 | 
			
		||||
    --_river-20: 86.91% 0.04221 273.1deg;
 | 
			
		||||
    --_river-30: 80.46% 0.05274 272.7deg;
 | 
			
		||||
    --_river-40: 74.01% 0.06326 272.4deg;
 | 
			
		||||
    --_river-50: 67.57% 0.07378 272.0deg;
 | 
			
		||||
    --_river-60: 61.12% 0.08430 271.7deg;
 | 
			
		||||
    --_river-70: 54.67% 0.09483 271.4deg;
 | 
			
		||||
    --_river-80: 48.22% 0.1053 271.0deg;
 | 
			
		||||
    --_river-90: 41.78% 0.1159 270.7deg;
 | 
			
		||||
    --_river-100: 35.33% 0.1264 270.4deg;
 | 
			
		||||
    --_river-110: 28.88% 0.1369 270.0deg;
 | 
			
		||||
    --_river-120: 22.44% 0.1474 269.7deg;
 | 
			
		||||
 | 
			
		||||
    /* Berry */
 | 
			
		||||
    --_berry-10: 93.77% 0.05212 329.0deg;
 | 
			
		||||
    --_berry-20: 87.30% 0.05912 325.3deg;
 | 
			
		||||
    --_berry-30: 80.82% 0.06612 321.6deg;
 | 
			
		||||
    --_berry-40: 74.34% 0.07313 317.8deg;
 | 
			
		||||
    --_berry-50: 67.86% 0.08013 314.1deg;
 | 
			
		||||
    --_berry-60: 61.39% 0.08713 310.3deg;
 | 
			
		||||
    --_berry-70: 54.91% 0.09413 306.6deg;
 | 
			
		||||
    --_berry-80: 48.43% 0.1011 302.8deg;
 | 
			
		||||
    --_berry-90: 41.95% 0.1081 299.1deg;
 | 
			
		||||
    --_berry-100: 35.47% 0.1151 295.4deg;
 | 
			
		||||
    --_berry-110: 29.00% 0.1221 291.6deg;
 | 
			
		||||
    --_berry-120: 22.52% 0.1291 287.9deg;
 | 
			
		||||
 | 
			
		||||
    /* Destroy */
 | 
			
		||||
    --_destroy-10: 88.21% 0.06281 14.85deg;
 | 
			
		||||
    --_destroy-20: 83.23% 0.08511 16.91deg;
 | 
			
		||||
    --_destroy-30: 78.25% 0.1074 18.96deg;
 | 
			
		||||
    --_destroy-40: 73.27% 0.1297 21.01deg;
 | 
			
		||||
    --_destroy-50: 68.29% 0.1520 23.07deg;
 | 
			
		||||
    --_destroy-60: 63.31% 0.1743 25.12deg;
 | 
			
		||||
    --_destroy-70: 58.33% 0.1966 27.18deg;
 | 
			
		||||
    --_destroy-80: 53.35% 0.2189 29.23deg;
 | 
			
		||||
 | 
			
		||||
    /* Warn */
 | 
			
		||||
    --_warn-10: 90.19% 0.1361 92.00deg;
 | 
			
		||||
    --_warn-20: 84.60% 0.1388 84.84deg;
 | 
			
		||||
    --_warn-30: 79.01% 0.1414 77.68deg;
 | 
			
		||||
    --_warn-40: 73.42% 0.1440 70.52deg;
 | 
			
		||||
    --_warn-50: 67.83% 0.1466 63.36deg;
 | 
			
		||||
    --_warn-60: 62.24% 0.1492 56.20deg;
 | 
			
		||||
    --_warn-70: 56.65% 0.1518 49.04deg;
 | 
			
		||||
    --_warn-80: 51.06% 0.1544 41.88deg;
 | 
			
		||||
 | 
			
		||||
    /* Succeed */
 | 
			
		||||
    --_succeed-10: 89.00% 0.1600 143.4deg;
 | 
			
		||||
    --_succeed-20: 83.23% 0.1608 143.3deg;
 | 
			
		||||
    --_succeed-30: 77.46% 0.1616 143.1deg;
 | 
			
		||||
    --_succeed-40: 71.69% 0.1623 143.0deg;
 | 
			
		||||
    --_succeed-50: 65.92% 0.1631 142.9deg;
 | 
			
		||||
    --_succeed-60: 60.16% 0.1639 142.8deg;
 | 
			
		||||
    --_succeed-70: 54.39% 0.1647 142.6deg;
 | 
			
		||||
    --_succeed-80: 48.62% 0.1654 142.5deg;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/components/ActionButton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/components/ActionButton.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
import { Link } from 'react-router-dom'
 | 
			
		||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
 | 
			
		||||
 | 
			
		||||
interface ActionButtonProps extends React.PropsWithChildren {
 | 
			
		||||
  icon?: ActionIconProps
 | 
			
		||||
  className?: string
 | 
			
		||||
  onClick?: () => void
 | 
			
		||||
  to?: string
 | 
			
		||||
  as?: 'button' | 'link'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ActionButton = ({
 | 
			
		||||
  icon,
 | 
			
		||||
  className,
 | 
			
		||||
  onClick,
 | 
			
		||||
  to = '/',
 | 
			
		||||
  as = 'button',
 | 
			
		||||
  children,
 | 
			
		||||
}: ActionButtonProps) => {
 | 
			
		||||
  const classNames = `group mono flex items-center gap-2 text-chalkboard-110 rounded-sm border border-chalkboard-40 hover:border-liquid-40 p-[3px] ${
 | 
			
		||||
    icon ? 'pr-2' : 'px-2'
 | 
			
		||||
  } ${className}`
 | 
			
		||||
 | 
			
		||||
  return as === 'button' ? (
 | 
			
		||||
    <button onClick={onClick} className={classNames}>
 | 
			
		||||
      {icon && <ActionIcon {...icon} />}
 | 
			
		||||
      {children}
 | 
			
		||||
    </button>
 | 
			
		||||
  ) : (
 | 
			
		||||
    <Link to={to} className={classNames}>
 | 
			
		||||
      {icon && <ActionIcon {...icon} />}
 | 
			
		||||
      {children}
 | 
			
		||||
    </Link>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								src/components/ActionIcon.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/components/ActionIcon.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
			
		||||
import {
 | 
			
		||||
  IconDefinition,
 | 
			
		||||
  faCircleExclamation,
 | 
			
		||||
} from '@fortawesome/free-solid-svg-icons'
 | 
			
		||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
 | 
			
		||||
 | 
			
		||||
const iconSizes = {
 | 
			
		||||
  sm: 12,
 | 
			
		||||
  md: 14.4,
 | 
			
		||||
  lg: 18,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ActionIconProps extends React.PropsWithChildren {
 | 
			
		||||
  icon?: IconDefinition
 | 
			
		||||
  bgClassName?: string
 | 
			
		||||
  iconClassName?: string
 | 
			
		||||
  size?: keyof typeof iconSizes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ActionIcon = ({
 | 
			
		||||
  icon,
 | 
			
		||||
  bgClassName,
 | 
			
		||||
  iconClassName,
 | 
			
		||||
  size = 'md',
 | 
			
		||||
  children,
 | 
			
		||||
}: ActionIconProps) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className={
 | 
			
		||||
        'p-1 w-fit inline-grid place-content-center ' +
 | 
			
		||||
        (bgClassName ||
 | 
			
		||||
          'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90')
 | 
			
		||||
      }
 | 
			
		||||
    >
 | 
			
		||||
      {children || (
 | 
			
		||||
        <FontAwesomeIcon
 | 
			
		||||
          icon={icon || faCircleExclamation}
 | 
			
		||||
          width={iconSizes[size]}
 | 
			
		||||
          height={iconSizes[size]}
 | 
			
		||||
          className={
 | 
			
		||||
            iconClassName ||
 | 
			
		||||
            'text-liquid-20 group-hover:text-liquid-10 hover:text-liquid-10'
 | 
			
		||||
          }
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/components/AppHeader.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/components/AppHeader.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
import { faGear } from '@fortawesome/free-solid-svg-icons'
 | 
			
		||||
import { Toolbar } from '../Toolbar'
 | 
			
		||||
import { ActionButton } from './ActionButton'
 | 
			
		||||
 | 
			
		||||
interface AppHeaderProps extends React.PropsWithChildren {
 | 
			
		||||
  showToolbar?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AppHeader = ({ showToolbar = true, children }: AppHeaderProps) => {
 | 
			
		||||
  return (
 | 
			
		||||
    <header className="py-1 px-5 bg-chalkboard-10 border-b border-chalkboard-30 flex justify-between items-center">
 | 
			
		||||
      <a href="/project-settings">
 | 
			
		||||
        <img
 | 
			
		||||
          src="/kitt-arcade-winking.svg"
 | 
			
		||||
          alt="KittyCAD App"
 | 
			
		||||
          className="h-9 w-auto"
 | 
			
		||||
        />
 | 
			
		||||
        <span className="sr-only">KittyCAD App</span>
 | 
			
		||||
      </a>
 | 
			
		||||
      {/* Toolbar if the context deems it */}
 | 
			
		||||
      {showToolbar && (
 | 
			
		||||
        <div className="max-w-4xl">
 | 
			
		||||
          <Toolbar />
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
      {/* If there are children, show them, otherwise... */}
 | 
			
		||||
      {children || (
 | 
			
		||||
        // TODO: If signed out, show the token paste field
 | 
			
		||||
 | 
			
		||||
        // If signed in, show the account avatar
 | 
			
		||||
        <ActionButton as="link" icon={{ icon: faGear }} to="/settings">
 | 
			
		||||
          Settings
 | 
			
		||||
        </ActionButton>
 | 
			
		||||
      )}
 | 
			
		||||
    </header>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								src/components/ErrorPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/components/ErrorPage.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
export const ErrorPage = () => {
 | 
			
		||||
  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>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@ -3,6 +3,7 @@
 | 
			
		||||
@tailwind utilities;
 | 
			
		||||
 | 
			
		||||
@import '../node_modules/allotment/dist/style.css';
 | 
			
		||||
@import './colors.css';
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
@ -13,6 +14,11 @@ body {
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mono {
 | 
			
		||||
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
 | 
			
		||||
    monospace;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
code {
 | 
			
		||||
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
 | 
			
		||||
    monospace;
 | 
			
		||||
 | 
			
		||||
@ -2,11 +2,13 @@ import ReactDOM from 'react-dom/client'
 | 
			
		||||
import './index.css'
 | 
			
		||||
import { Auth } from './Auth'
 | 
			
		||||
import reportWebVitals from './reportWebVitals'
 | 
			
		||||
import { Toaster } from 'react-hot-toast'
 | 
			
		||||
 | 
			
		||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
 | 
			
		||||
root.render(
 | 
			
		||||
root.render(<>
 | 
			
		||||
  <Auth />
 | 
			
		||||
)
 | 
			
		||||
  <Toaster position='bottom-center' />
 | 
			
		||||
</>)
 | 
			
		||||
 | 
			
		||||
// If you want to start measuring performance in your app, pass a function
 | 
			
		||||
// to log results (for example: reportWebVitals(console.log))
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										143
									
								
								src/routes/Settings.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/routes/Settings.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,143 @@
 | 
			
		||||
import { faCheck, faFolder, faXmark } from '@fortawesome/free-solid-svg-icons'
 | 
			
		||||
import { ActionButton } from '../components/ActionButton'
 | 
			
		||||
import { AppHeader } from '../components/AppHeader'
 | 
			
		||||
import { open } from '@tauri-apps/api/dialog'
 | 
			
		||||
import { useStore } from '../useStore'
 | 
			
		||||
import { useState } from 'react'
 | 
			
		||||
import { toast } from 'react-hot-toast'
 | 
			
		||||
 | 
			
		||||
export const Settings = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    defaultDir: originalDefaultDir,
 | 
			
		||||
    setDefaultDir: saveDefaultDir,
 | 
			
		||||
    defaultProjectName: originalDefaultProjectName,
 | 
			
		||||
    setDefaultProjectName: saveDefaultProjectName,
 | 
			
		||||
  } = useStore((s) => ({
 | 
			
		||||
    defaultDir: s.defaultDir,
 | 
			
		||||
    setDefaultDir: s.setDefaultDir,
 | 
			
		||||
    defaultProjectName: s.defaultProjectName,
 | 
			
		||||
    setDefaultProjectName: s.setDefaultProjectName,
 | 
			
		||||
  }))
 | 
			
		||||
  const [defaultDir, setDefaultDir] = useState(originalDefaultDir)
 | 
			
		||||
  const [defaultProjectName, setDefaultProjectName] = useState(
 | 
			
		||||
    originalDefaultProjectName
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  async function handleDirectorySelection() {
 | 
			
		||||
    const newDirectory = await open({
 | 
			
		||||
      directory: true,
 | 
			
		||||
      defaultPath: (defaultDir.base || '') + (defaultDir.dir || '/'),
 | 
			
		||||
      title: 'Choose a new default directory',
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    if (newDirectory && newDirectory !== null && !Array.isArray(newDirectory)) {
 | 
			
		||||
      setDefaultDir({ base: defaultDir.base, dir: newDirectory })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleSaveClick = () => {
 | 
			
		||||
    saveDefaultDir(defaultDir)
 | 
			
		||||
    saveDefaultProjectName(defaultProjectName)
 | 
			
		||||
    toast.success('Settings saved!')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <AppHeader showToolbar={false}>
 | 
			
		||||
        <ActionButton
 | 
			
		||||
          as="link"
 | 
			
		||||
          to="/"
 | 
			
		||||
          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"
 | 
			
		||||
        >
 | 
			
		||||
          Close
 | 
			
		||||
        </ActionButton>
 | 
			
		||||
      </AppHeader>
 | 
			
		||||
      <div className="mt-24 max-w-5xl mx-auto">
 | 
			
		||||
        <h1 className="text-4xl font-bold">User Settings</h1>
 | 
			
		||||
        {(window as any).__TAURI__ && (
 | 
			
		||||
          <SettingsSection
 | 
			
		||||
            title="Default Directory"
 | 
			
		||||
            description="Where newly-created projects are saved on your local computer"
 | 
			
		||||
          >
 | 
			
		||||
            <div className="w-full flex gap-4 p-1 rounded border border-chalkboard-30">
 | 
			
		||||
              <input
 | 
			
		||||
                className="flex-1 px-2 bg-transparent"
 | 
			
		||||
                value={defaultDir.dir}
 | 
			
		||||
                onChange={(e) =>
 | 
			
		||||
                  setDefaultDir({
 | 
			
		||||
                    base: originalDefaultDir.base,
 | 
			
		||||
                    dir: e.target.value,
 | 
			
		||||
                  })
 | 
			
		||||
                }
 | 
			
		||||
              />
 | 
			
		||||
              <ActionButton
 | 
			
		||||
                as="button"
 | 
			
		||||
                className="bg-chalkboard-100 hover:bg-chalkboard-90 text-chalkboard-10 border-chalkboard-100 hover:border-chalkboard-70"
 | 
			
		||||
                onClick={handleDirectorySelection}
 | 
			
		||||
                icon={{
 | 
			
		||||
                  icon: faFolder,
 | 
			
		||||
                  bgClassName:
 | 
			
		||||
                    'bg-liquid-20 group-hover:bg-liquid-10 hover:bg-liquid-10',
 | 
			
		||||
                  iconClassName:
 | 
			
		||||
                    'text-liquid-90 group-hover:text-liquid-90 hover:text-liquid-90',
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                Choose a folder
 | 
			
		||||
              </ActionButton>
 | 
			
		||||
            </div>
 | 
			
		||||
          </SettingsSection>
 | 
			
		||||
        )}
 | 
			
		||||
        <SettingsSection
 | 
			
		||||
          title="Default Project Name"
 | 
			
		||||
          description="Name template for new projects. Use $n to include an incrementing index"
 | 
			
		||||
        >
 | 
			
		||||
          <input
 | 
			
		||||
            className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
 | 
			
		||||
            value={defaultProjectName}
 | 
			
		||||
            onChange={(e) => setDefaultProjectName(e.target.value)}
 | 
			
		||||
          />
 | 
			
		||||
        </SettingsSection>
 | 
			
		||||
        <ActionButton
 | 
			
		||||
          className="hover:border-succeed-50"
 | 
			
		||||
          onClick={handleSaveClick}
 | 
			
		||||
          icon={{
 | 
			
		||||
            icon: faCheck,
 | 
			
		||||
            bgClassName:
 | 
			
		||||
              'bg-succeed-80 group-hover:bg-succeed-70 hover:bg-succeed-70',
 | 
			
		||||
            iconClassName:
 | 
			
		||||
              'text-succeed-20 group-hover:text-succeed-10 hover:text-succeed-10',
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          Save Settings
 | 
			
		||||
        </ActionButton>
 | 
			
		||||
      </div>
 | 
			
		||||
    </>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface SettingsSectionProps extends React.PropsWithChildren {
 | 
			
		||||
  title: string
 | 
			
		||||
  description?: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function SettingsSection({
 | 
			
		||||
  title,
 | 
			
		||||
  description,
 | 
			
		||||
  children,
 | 
			
		||||
}: SettingsSectionProps) {
 | 
			
		||||
  return (
 | 
			
		||||
    <section className="my-8 first-of-type:mt-16 last-of-type:mb-16 flex gap-12 items-start">
 | 
			
		||||
      <div className="w-80">
 | 
			
		||||
        <h2 className="text-2xl">{title}</h2>
 | 
			
		||||
        <p className="mt-2 text-sm">{description}</p>
 | 
			
		||||
      </div>
 | 
			
		||||
      {children}
 | 
			
		||||
    </section>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
@ -155,6 +155,8 @@ export interface StoreState {
 | 
			
		||||
  // tauri specific app settings
 | 
			
		||||
  defaultDir: DefaultDir
 | 
			
		||||
  setDefaultDir: (dir: DefaultDir) => void
 | 
			
		||||
  defaultProjectName: string
 | 
			
		||||
  setDefaultProjectName: (defaultProjectName: string) => void
 | 
			
		||||
  showHomeMenu: boolean
 | 
			
		||||
  setHomeShowMenu: (showMenu: boolean) => void
 | 
			
		||||
  homeMenuItems: {
 | 
			
		||||
@ -310,9 +312,11 @@ export const useStore = create<StoreState>()(
 | 
			
		||||
  
 | 
			
		||||
      // tauri specific app settings
 | 
			
		||||
      defaultDir: {
 | 
			
		||||
        dir: '',
 | 
			
		||||
        dir: '~/Documents/',
 | 
			
		||||
      },
 | 
			
		||||
      setDefaultDir: (dir) => set({ defaultDir: dir }),
 | 
			
		||||
      defaultProjectName: 'new-project-$n',
 | 
			
		||||
      setDefaultProjectName: (defaultProjectName) => set({ defaultProjectName }),
 | 
			
		||||
      showHomeMenu: true,
 | 
			
		||||
      setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
 | 
			
		||||
      homeMenuItems: [],
 | 
			
		||||
@ -324,7 +328,12 @@ export const useStore = create<StoreState>()(
 | 
			
		||||
      name: 'store',
 | 
			
		||||
      partialize: (state) =>
 | 
			
		||||
        Object.fromEntries(
 | 
			
		||||
          Object.entries(state).filter(([key]) => ['code', 'defaultDir', 'token'].includes(key))
 | 
			
		||||
          Object.entries(state).filter(([key]) => [
 | 
			
		||||
            'code',
 | 
			
		||||
            'defaultDir',
 | 
			
		||||
            'defaultProjectName',
 | 
			
		||||
            'token',
 | 
			
		||||
          ].includes(key))
 | 
			
		||||
        ),
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,42 @@
 | 
			
		||||
const themeColorRamps = [
 | 
			
		||||
  { name: 'chalkboard', stops: 12 },
 | 
			
		||||
  { name: 'energy', stops: 12 },
 | 
			
		||||
  { name: 'liquid', stops: 12 },
 | 
			
		||||
  { name: 'fern', stops: 12 },
 | 
			
		||||
  { name: 'cool', stops: 12 },
 | 
			
		||||
  { name: 'river', stops: 12 },
 | 
			
		||||
  { name: 'berry', stops: 12 },
 | 
			
		||||
  { name: 'destroy', stops: 8 },
 | 
			
		||||
  { name: 'warn', stops: 8 },
 | 
			
		||||
  { name: 'succeed', stops: 8 },
 | 
			
		||||
]
 | 
			
		||||
const toOKLCHVar = val => `oklch(var(${val}) / <alpha-value>) `
 | 
			
		||||
 | 
			
		||||
const themeColors = Object.fromEntries(
 | 
			
		||||
  themeColorRamps.map(({name, stops}) => [
 | 
			
		||||
      name,
 | 
			
		||||
      Object.fromEntries(
 | 
			
		||||
          new Array(stops)
 | 
			
		||||
              .fill(0)
 | 
			
		||||
              .map((_, i) => [
 | 
			
		||||
                  (i + 1) * 10,
 | 
			
		||||
                  toOKLCHVar(`--_${name}-${(i + 1) * 10}`),
 | 
			
		||||
              ])
 | 
			
		||||
      ),
 | 
			
		||||
  ])
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/** @type {import('tailwindcss').Config} */
 | 
			
		||||
module.exports = {
 | 
			
		||||
  content: [
 | 
			
		||||
    "./src/**/*.{js,jsx,ts,tsx}",
 | 
			
		||||
  ],
 | 
			
		||||
  theme: {
 | 
			
		||||
    extend: {},
 | 
			
		||||
    extend: {
 | 
			
		||||
      colors: {
 | 
			
		||||
        ...themeColors,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  plugins: [],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										58
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								yarn.lock
									
									
									
									
									
								
							@ -1539,6 +1539,32 @@
 | 
			
		||||
    minimatch "^3.1.2"
 | 
			
		||||
    strip-json-comments "^3.1.1"
 | 
			
		||||
 | 
			
		||||
"@fortawesome/fontawesome-common-types@6.4.0":
 | 
			
		||||
  version "6.4.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz#88da2b70d6ca18aaa6ed3687832e11f39e80624b"
 | 
			
		||||
  integrity sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==
 | 
			
		||||
 | 
			
		||||
"@fortawesome/fontawesome-svg-core@^6.4.0":
 | 
			
		||||
  version "6.4.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz#3727552eff9179506e9203d72feb5b1063c11a21"
 | 
			
		||||
  integrity sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@fortawesome/fontawesome-common-types" "6.4.0"
 | 
			
		||||
 | 
			
		||||
"@fortawesome/free-solid-svg-icons@^6.4.0":
 | 
			
		||||
  version "6.4.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz#48c0e790847fa56299e2f26b82b39663b8ad7119"
 | 
			
		||||
  integrity sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@fortawesome/fontawesome-common-types" "6.4.0"
 | 
			
		||||
 | 
			
		||||
"@fortawesome/react-fontawesome@^0.2.0":
 | 
			
		||||
  version "0.2.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz#d90dd8a9211830b4e3c08e94b63a0ba7291ddcf4"
 | 
			
		||||
  integrity sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    prop-types "^15.8.1"
 | 
			
		||||
 | 
			
		||||
"@headlessui/react@^1.7.13":
 | 
			
		||||
  version "1.7.13"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.13.tgz#fd150b394954e9f1d86ed2340cffd1217d6e7628"
 | 
			
		||||
@ -1958,6 +1984,11 @@
 | 
			
		||||
    schema-utils "^3.0.0"
 | 
			
		||||
    source-map "^0.7.3"
 | 
			
		||||
 | 
			
		||||
"@remix-run/router@1.7.1":
 | 
			
		||||
  version "1.7.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.1.tgz#fea7ac35ae4014637c130011f59428f618730498"
 | 
			
		||||
  integrity sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ==
 | 
			
		||||
 | 
			
		||||
"@rollup/plugin-babel@^5.2.0":
 | 
			
		||||
  version "5.3.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
 | 
			
		||||
@ -5568,6 +5599,11 @@ globby@^11.0.4, globby@^11.1.0:
 | 
			
		||||
    merge2 "^1.4.1"
 | 
			
		||||
    slash "^3.0.0"
 | 
			
		||||
 | 
			
		||||
goober@^2.1.10:
 | 
			
		||||
  version "2.1.13"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.13.tgz#e3c06d5578486212a76c9eba860cbc3232ff6d7c"
 | 
			
		||||
  integrity sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==
 | 
			
		||||
 | 
			
		||||
gopd@^1.0.1:
 | 
			
		||||
  version "1.0.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
 | 
			
		||||
@ -8802,6 +8838,13 @@ react-error-overlay@^6.0.11:
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
 | 
			
		||||
  integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
 | 
			
		||||
 | 
			
		||||
react-hot-toast@^2.4.1:
 | 
			
		||||
  version "2.4.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.4.1.tgz#df04295eda8a7b12c4f968e54a61c8d36f4c0994"
 | 
			
		||||
  integrity sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    goober "^2.1.10"
 | 
			
		||||
 | 
			
		||||
react-is@^16.13.1:
 | 
			
		||||
  version "16.13.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
 | 
			
		||||
@ -8842,6 +8885,21 @@ react-refresh@^0.11.0:
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
 | 
			
		||||
  integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
 | 
			
		||||
 | 
			
		||||
react-router-dom@^6.14.1:
 | 
			
		||||
  version "6.14.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.1.tgz#0ad7ba7abdf75baa61169d49f096f0494907a36f"
 | 
			
		||||
  integrity sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@remix-run/router" "1.7.1"
 | 
			
		||||
    react-router "6.14.1"
 | 
			
		||||
 | 
			
		||||
react-router@6.14.1:
 | 
			
		||||
  version "6.14.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.1.tgz#5e82bcdabf21add859dc04b1859f91066b3a5810"
 | 
			
		||||
  integrity sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    "@remix-run/router" "1.7.1"
 | 
			
		||||
 | 
			
		||||
react-scripts@5.0.1:
 | 
			
		||||
  version "5.0.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003"
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user