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:
Frank Noirot
2023-07-13 07:22:08 -04:00
committed by GitHub
parent 3fc4d71a1e
commit 59fa51d75a
17 changed files with 731 additions and 10 deletions

View File

@ -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",

View 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

View 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

View File

@ -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()
})

View File

@ -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>

View File

@ -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
View 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;
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View File

@ -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;

View File

@ -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
View 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>
)
}

View File

@ -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))
),
}
)

View File

@ -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: [],
}

View File

@ -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"