This reverts commit 602e7afef6
.
This commit is contained in:
@ -3,7 +3,6 @@ import { secrets } from './secrets'
|
||||
import { getUtils } from './test-utils'
|
||||
import waitOn from 'wait-on'
|
||||
import { Themes } from '../../src/lib/theme'
|
||||
import { initialSettings } from '../../src/lib/settings'
|
||||
|
||||
/*
|
||||
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
|
||||
@ -387,53 +386,6 @@ test('Auto complete works', async ({ page }) => {
|
||||
|> xLine(5, %)`)
|
||||
})
|
||||
|
||||
// Stored settings validation test
|
||||
test('Stored settings are validated and fall back to defaults', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
// Override beforeEach test setup
|
||||
// with corrupted settings
|
||||
await context.addInitScript(async () => {
|
||||
const storedSettings = JSON.parse(
|
||||
localStorage.getItem('SETTINGS_PERSIST_KEY') || '{}'
|
||||
)
|
||||
|
||||
// Corrupt the settings
|
||||
storedSettings.baseUnit = 'invalid'
|
||||
storedSettings.cameraControls = `() => alert('hack the planet')`
|
||||
storedSettings.defaultDirectory = 123
|
||||
storedSettings.defaultProjectName = false
|
||||
|
||||
localStorage.setItem('SETTINGS_PERSIST_KEY', JSON.stringify(storedSettings))
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
// Check the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Error validating persisted settings:`, { exact: false })
|
||||
).toBeVisible()
|
||||
|
||||
// Check the settings were reset
|
||||
const storedSettings = JSON.parse(
|
||||
await page.evaluate(
|
||||
() => localStorage.getItem('SETTINGS_PERSIST_KEY') || '{}'
|
||||
)
|
||||
)
|
||||
await expect(storedSettings.baseUnit).toBe(initialSettings.baseUnit)
|
||||
await expect(storedSettings.cameraControls).toBe(
|
||||
initialSettings.cameraControls
|
||||
)
|
||||
await expect(storedSettings.defaultDirectory).toBe(
|
||||
initialSettings.defaultDirectory
|
||||
)
|
||||
await expect(storedSettings.defaultProjectName).toBe(
|
||||
initialSettings.defaultProjectName
|
||||
)
|
||||
})
|
||||
|
||||
// Onboarding tests
|
||||
test('Onboarding redirects and code updating', async ({ page, context }) => {
|
||||
const u = getUtils(page)
|
||||
|
@ -143,25 +143,6 @@ async fn get_user(
|
||||
Ok(user_info)
|
||||
}
|
||||
|
||||
/// Open the selected path in the system file manager.
|
||||
/// From this GitHub comment: https://github.com/tauri-apps/tauri/issues/4062#issuecomment-1338048169
|
||||
/// But with the Linux support removed since we don't need it for now.
|
||||
#[tauri::command]
|
||||
fn show_in_folder(path: String) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Command::new("explorer")
|
||||
.args(["/select,", &path]) // The comma after select is not a typo
|
||||
.spawn()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
Command::new("open").args(["-R", &path]).spawn().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.setup(|_app| {
|
||||
@ -178,8 +159,7 @@ fn main() {
|
||||
get_user,
|
||||
login,
|
||||
read_toml,
|
||||
read_txt_file,
|
||||
show_in_folder,
|
||||
read_txt_file
|
||||
])
|
||||
.plugin(tauri_plugin_fs_extra::init())
|
||||
.run(tauri::generate_context!())
|
||||
|
@ -23,8 +23,7 @@
|
||||
"fs": {
|
||||
"scope": [
|
||||
"$HOME/**/*",
|
||||
"$APPDATA/**/*",
|
||||
"$DOCUMENT/**/*"
|
||||
"$APPDATA/**/*"
|
||||
],
|
||||
"all": true
|
||||
},
|
||||
@ -61,7 +60,7 @@
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"identifier": "zoo-modeling-app",
|
||||
"identifier": "io.kittycad.modeling-app",
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": null,
|
||||
|
@ -22,7 +22,7 @@ import { getNormalisedCoordinates } from './lib/utils'
|
||||
import { useLoaderData, useNavigate } from 'react-router-dom'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { paths } from 'lib/paths'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { CodeMenu } from 'components/CodeMenu'
|
||||
import { TextEditor } from 'components/TextEditor'
|
||||
@ -53,7 +53,7 @@ export function App() {
|
||||
streamDimensions: s.streamDimensions,
|
||||
}))
|
||||
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { settings } = useGlobalStateContext()
|
||||
const { showDebugPanel, onboardingStatus, theme } = settings?.context || {}
|
||||
const { state, send } = useModelingContext()
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Loading from './components/Loading'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
|
||||
// Wrapper around protected routes, used in src/Router.tsx
|
||||
export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const { auth } = useGlobalStateContext()
|
||||
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
|
||||
|
||||
return isLoggingIn ? (
|
||||
|
@ -29,9 +29,11 @@ import {
|
||||
import { metadata } from 'tauri-plugin-fs-extra-api'
|
||||
import DownloadAppBanner from './components/DownloadAppBanner'
|
||||
import { WasmErrBanner } from './components/WasmErrBanner'
|
||||
import { SettingsAuthStateProvider } from './components/SettingsAuthStateProvider'
|
||||
import { settingsMachine } from './machines/settingsMachine'
|
||||
import { SETTINGS_PERSIST_KEY } from 'lib/settings'
|
||||
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
||||
import {
|
||||
SETTINGS_PERSIST_KEY,
|
||||
settingsMachine,
|
||||
} from './machines/settingsMachine'
|
||||
import { ContextFrom } from 'xstate'
|
||||
import CommandBarProvider from 'components/CommandBar/CommandBar'
|
||||
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
||||
@ -89,9 +91,7 @@ const addGlobalContextToElements = (
|
||||
...route,
|
||||
element: (
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthStateProvider>
|
||||
{route.element}
|
||||
</SettingsAuthStateProvider>
|
||||
<GlobalStateProvider>{route.element}</GlobalStateProvider>
|
||||
</CommandBarProvider>
|
||||
),
|
||||
}
|
||||
@ -229,10 +229,8 @@ const router = createBrowserRouter(
|
||||
const projectDir = await initializeProjectDirectory(
|
||||
persistedSettings.defaultDirectory || ''
|
||||
)
|
||||
|
||||
let newDefaultDirectory: string | undefined = undefined
|
||||
if (projectDir.path) {
|
||||
if (projectDir.path !== persistedSettings.defaultDirectory) {
|
||||
if (projectDir !== persistedSettings.defaultDirectory) {
|
||||
localStorage.setItem(
|
||||
SETTINGS_PERSIST_KEY,
|
||||
JSON.stringify({
|
||||
@ -240,9 +238,9 @@ const router = createBrowserRouter(
|
||||
defaultDirectory: projectDir,
|
||||
})
|
||||
)
|
||||
newDefaultDirectory = projectDir.path
|
||||
newDefaultDirectory = projectDir
|
||||
}
|
||||
const projectsNoMeta = (await readDir(projectDir.path)).filter(
|
||||
const projectsNoMeta = (await readDir(projectDir)).filter(
|
||||
isProjectDirectory
|
||||
)
|
||||
const projects = await Promise.all(
|
||||
@ -257,14 +255,6 @@ const router = createBrowserRouter(
|
||||
return {
|
||||
projects,
|
||||
newDefaultDirectory,
|
||||
error: projectDir.error,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
projects: [],
|
||||
newDefaultDirectory,
|
||||
error: projectDir.error,
|
||||
}
|
||||
}
|
||||
},
|
||||
children: [
|
||||
|
@ -2,7 +2,7 @@ import { useRef, useEffect, useState } from 'react'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { useStore } from 'useStore'
|
||||
import {
|
||||
DEBUG_SHOW_BOTH_SCENES,
|
||||
@ -38,7 +38,7 @@ export const ClientSideScene = ({
|
||||
cameraControls,
|
||||
}: {
|
||||
cameraControls: ReturnType<
|
||||
typeof useSettingsAuthContext
|
||||
typeof useGlobalStateContext
|
||||
>['settings']['context']['cameraControls']
|
||||
}) => {
|
||||
const canvasRef = useRef<HTMLDivElement>(null)
|
||||
|
@ -2,7 +2,7 @@ import { Toolbar } from '../Toolbar'
|
||||
import UserSidebarMenu from './UserSidebarMenu'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import styles from './AppHeader.module.css'
|
||||
import { NetworkHealthIndicator } from './NetworkHealthIndicator'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
@ -25,7 +25,7 @@ export const AppHeader = ({
|
||||
}: AppHeaderProps) => {
|
||||
const platform = usePlatform()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const { auth } = useGlobalStateContext()
|
||||
const user = auth?.context?.user
|
||||
|
||||
return (
|
||||
|
@ -6,7 +6,7 @@ import React from 'react'
|
||||
import { useFormik } from 'formik'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
|
||||
type OutputFormat = Models['OutputFormat_type']
|
||||
type OutputTypeKey = OutputFormat['type']
|
||||
@ -29,7 +29,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
||||
context: { baseUnit },
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
|
||||
const defaultType = 'gltf'
|
||||
const [type, setType] = React.useState<OutputTypeKey>(defaultType)
|
||||
|
@ -5,12 +5,7 @@ import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
|
||||
import withBaseUrl from '../lib/withBaseURL'
|
||||
import React, { createContext, useEffect, useRef } from 'react'
|
||||
import useStateMachineCommands from '../hooks/useStateMachineCommands'
|
||||
import { settingsMachine } from 'machines/settingsMachine'
|
||||
import {
|
||||
initialSettings,
|
||||
SETTINGS_PERSIST_KEY,
|
||||
validateSettings,
|
||||
} from 'lib/settings'
|
||||
import { SETTINGS_PERSIST_KEY, settingsMachine } from 'machines/settingsMachine'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { setThemeClass, Themes } from 'lib/theme'
|
||||
import {
|
||||
@ -23,7 +18,6 @@ import {
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { settingsCommandBarConfig } from 'lib/commandBarConfigs/settingsCommandConfig'
|
||||
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
||||
import { initializeProjectDirectory, readSettingsFile } from 'lib/tauriFS'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -31,14 +25,14 @@ type MachineContext<T extends AnyStateMachine> = {
|
||||
send: Prop<InterpreterFrom<T>, 'send'>
|
||||
}
|
||||
|
||||
type SettingsAuthContext = {
|
||||
type GlobalContext = {
|
||||
auth: MachineContext<typeof authMachine>
|
||||
settings: MachineContext<typeof settingsMachine>
|
||||
}
|
||||
|
||||
export const SettingsAuthStateContext = createContext({} as SettingsAuthContext)
|
||||
export const GlobalStateContext = createContext({} as GlobalContext)
|
||||
|
||||
export const SettingsAuthStateProvider = ({
|
||||
export const GlobalStateProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
@ -46,17 +40,14 @@ export const SettingsAuthStateProvider = ({
|
||||
const navigate = useNavigate()
|
||||
|
||||
// Settings machine setup
|
||||
// Load settings from local storage
|
||||
// and validate them
|
||||
const retrievedSettings = useRef(
|
||||
validateSettings(
|
||||
JSON.parse(localStorage?.getItem(SETTINGS_PERSIST_KEY) || '{}')
|
||||
)
|
||||
localStorage?.getItem(SETTINGS_PERSIST_KEY) || '{}'
|
||||
)
|
||||
const persistedSettings = Object.assign(
|
||||
{},
|
||||
initialSettings,
|
||||
retrievedSettings.current.settings
|
||||
settingsMachine.initialState.context,
|
||||
JSON.parse(retrievedSettings.current) as Partial<
|
||||
(typeof settingsMachine)['context']
|
||||
>
|
||||
)
|
||||
|
||||
const [settingsState, settingsSend] = useMachine(settingsMachine, {
|
||||
@ -81,75 +72,6 @@ export const SettingsAuthStateProvider = ({
|
||||
},
|
||||
})
|
||||
|
||||
// If the app is running in the Tauri context,
|
||||
// try to read the settings from a file
|
||||
// after doing some validation on them
|
||||
useEffect(() => {
|
||||
async function getFileBasedSettings() {
|
||||
if (isTauri()) {
|
||||
const newSettings = await readSettingsFile()
|
||||
|
||||
if (newSettings) {
|
||||
if (newSettings.defaultDirectory) {
|
||||
const newDefaultDirectory = await initializeProjectDirectory(
|
||||
newSettings.defaultDirectory || ''
|
||||
)
|
||||
if (newDefaultDirectory.error !== null) {
|
||||
toast.error(newDefaultDirectory.error.message)
|
||||
}
|
||||
|
||||
if (newDefaultDirectory.path !== null) {
|
||||
newSettings.defaultDirectory = newDefaultDirectory.path
|
||||
}
|
||||
}
|
||||
const { settings: validatedSettings, errors: validationErrors } =
|
||||
validateSettings(newSettings)
|
||||
|
||||
retrievedSettings.current = Object.assign(
|
||||
{},
|
||||
initialSettings,
|
||||
retrievedSettings.current,
|
||||
validatedSettings
|
||||
)
|
||||
|
||||
settingsSend({
|
||||
type: 'Set All Settings',
|
||||
data: validatedSettings,
|
||||
})
|
||||
|
||||
return validationErrors
|
||||
}
|
||||
} else {
|
||||
// If the app is not running in the Tauri context,
|
||||
// just use the settings from local storage
|
||||
// after they've been validated to ensure they are correct.
|
||||
settingsSend({
|
||||
type: 'Set All Settings',
|
||||
data: retrievedSettings.current.settings,
|
||||
})
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// If there were validation errors either from local storage or from the file,
|
||||
// log them to the console and show a toast message to the user.
|
||||
void getFileBasedSettings().then((validationErrors: string[]) => {
|
||||
const combinedErrors = new Set([
|
||||
...retrievedSettings.current.errors,
|
||||
...validationErrors,
|
||||
])
|
||||
|
||||
if (combinedErrors.size > 0) {
|
||||
const errorMessage =
|
||||
'Error validating persisted settings: ' +
|
||||
Array.from(combinedErrors).join(', ') +
|
||||
'. Using defaults.'
|
||||
console.error(errorMessage)
|
||||
toast.error(errorMessage)
|
||||
}
|
||||
})
|
||||
}, [settingsSend])
|
||||
|
||||
useStateMachineCommands({
|
||||
machineId: 'settings',
|
||||
state: settingsState,
|
||||
@ -197,7 +119,7 @@ export const SettingsAuthStateProvider = ({
|
||||
})
|
||||
|
||||
return (
|
||||
<SettingsAuthStateContext.Provider
|
||||
<GlobalStateContext.Provider
|
||||
value={{
|
||||
auth: {
|
||||
state: authState,
|
||||
@ -212,11 +134,11 @@ export const SettingsAuthStateProvider = ({
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SettingsAuthStateContext.Provider>
|
||||
</GlobalStateContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default SettingsAuthStateProvider
|
||||
export default GlobalStateProvider
|
||||
|
||||
export function logout() {
|
||||
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
@ -10,7 +10,7 @@ import {
|
||||
} from 'xstate'
|
||||
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
|
||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||
@ -53,7 +53,7 @@ export const ModelingMachineProvider = ({
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const { auth } = useGlobalStateContext()
|
||||
const { code } = useKclContext()
|
||||
const token = auth?.context?.token
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { SettingsAuthStateProvider } from './SettingsAuthStateProvider'
|
||||
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||
import CommandBarProvider from './CommandBar/CommandBar'
|
||||
import {
|
||||
NETWORK_HEALTH_TEXT,
|
||||
@ -13,7 +13,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthStateProvider>{children}</SettingsAuthStateProvider>
|
||||
<GlobalStateProvider>{children}</GlobalStateProvider>
|
||||
</CommandBarProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
||||
import { SettingsAuthStateProvider } from './SettingsAuthStateProvider'
|
||||
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||
import CommandBarProvider from './CommandBar/CommandBar'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
|
||||
@ -42,9 +42,9 @@ describe('ProjectSidebarMenu tests', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthStateProvider>
|
||||
<GlobalStateProvider>
|
||||
<ProjectSidebarMenu project={projectWellFormed} />
|
||||
</SettingsAuthStateProvider>
|
||||
</GlobalStateProvider>
|
||||
</CommandBarProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
@ -63,9 +63,9 @@ describe('ProjectSidebarMenu tests', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthStateProvider>
|
||||
<GlobalStateProvider>
|
||||
<ProjectSidebarMenu />
|
||||
</SettingsAuthStateProvider>
|
||||
</GlobalStateProvider>
|
||||
</CommandBarProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
@ -79,12 +79,12 @@ describe('ProjectSidebarMenu tests', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthStateProvider>
|
||||
<GlobalStateProvider>
|
||||
<ProjectSidebarMenu
|
||||
project={projectWellFormed}
|
||||
renderAsLink={true}
|
||||
/>
|
||||
</SettingsAuthStateProvider>
|
||||
</GlobalStateProvider>
|
||||
</CommandBarProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
|
@ -10,7 +10,7 @@ import { useStore } from '../useStore'
|
||||
import { getNormalisedCoordinates, throttle } from '../lib/utils'
|
||||
import Loading from './Loading'
|
||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
@ -34,7 +34,7 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
setDidDragInStream: s.setDidDragInStream,
|
||||
streamDimensions: s.streamDimensions,
|
||||
}))
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { settings } = useGlobalStateContext()
|
||||
const cameraControls = settings?.context?.cameraControls
|
||||
const { state } = useModelingContext()
|
||||
const { isExecuting } = useKclContext()
|
||||
|
@ -8,7 +8,7 @@ import Server from '../editor/lsp/server'
|
||||
import Client from '../editor/lsp/client'
|
||||
import { TEST } from 'env'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||
import { Themes } from 'lib/theme'
|
||||
import { useMemo, useRef } from 'react'
|
||||
@ -72,7 +72,7 @@ export const TextEditor = ({
|
||||
} = useModelingContext()
|
||||
|
||||
const { settings: { context: { textWrapping } = {} } = {}, auth } =
|
||||
useSettingsAuthContext()
|
||||
useGlobalStateContext()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { enable: convertEnabled, handleClick: convertCallback } =
|
||||
useConvertToVariable()
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
createRoutesFromElements,
|
||||
} from 'react-router-dom'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { SettingsAuthStateProvider } from './SettingsAuthStateProvider'
|
||||
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||
import CommandBarProvider from './CommandBar/CommandBar'
|
||||
|
||||
type User = Models['User_type']
|
||||
@ -107,7 +107,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
|
||||
path="/file/:id"
|
||||
element={
|
||||
<CommandBarProvider>
|
||||
<SettingsAuthStateProvider>{children}</SettingsAuthStateProvider>
|
||||
<GlobalStateProvider>{children}</GlobalStateProvider>
|
||||
</CommandBarProvider>
|
||||
}
|
||||
/>
|
||||
|
@ -6,7 +6,7 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { paths } from 'lib/paths'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
|
||||
type User = Models['User_type']
|
||||
@ -17,7 +17,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
const displayedName = getDisplayName(user)
|
||||
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
const send = useSettingsAuthContext()?.auth?.send
|
||||
const send = useGlobalStateContext()?.auth?.send
|
||||
|
||||
// Fallback logic for displaying user's "name":
|
||||
// 1. user.name
|
||||
|
6
src/hooks/useGlobalStateContext.ts
Normal file
6
src/hooks/useGlobalStateContext.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { GlobalStateContext } from 'components/GlobalStateProvider'
|
||||
import { useContext } from 'react'
|
||||
|
||||
export const useGlobalStateContext = () => {
|
||||
return useContext(GlobalStateContext)
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import { SettingsAuthStateContext } from 'components/SettingsAuthStateProvider'
|
||||
import { useContext } from 'react'
|
||||
|
||||
export const useSettingsAuthContext = () => {
|
||||
return useContext(SettingsAuthStateContext)
|
||||
}
|
@ -1,6 +1,11 @@
|
||||
import { CommandSetConfig } from '../commandTypes'
|
||||
import { BaseUnit, Toggle, UnitSystem, baseUnitsUnion } from 'lib/settings'
|
||||
import { settingsMachine } from 'machines/settingsMachine'
|
||||
import {
|
||||
BaseUnit,
|
||||
Toggle,
|
||||
UnitSystem,
|
||||
baseUnitsUnion,
|
||||
settingsMachine,
|
||||
} from 'machines/settingsMachine'
|
||||
import { CameraSystem, cameraSystems } from '../cameraControls'
|
||||
import { Themes } from '../theme'
|
||||
|
||||
|
@ -1,95 +0,0 @@
|
||||
import { type Models } from '@kittycad/lib'
|
||||
import { CameraSystem, cameraSystems } from './cameraControls'
|
||||
import { Themes } from './theme'
|
||||
|
||||
export const DEFAULT_PROJECT_NAME = 'project-$nnn'
|
||||
export const SETTINGS_PERSIST_KEY = 'SETTINGS_PERSIST_KEY'
|
||||
export const SETTINGS_FILE_NAME = 'settings.json'
|
||||
|
||||
export enum UnitSystem {
|
||||
Imperial = 'imperial',
|
||||
Metric = 'metric',
|
||||
}
|
||||
|
||||
export const baseUnits = {
|
||||
imperial: ['in', 'ft', 'yd'],
|
||||
metric: ['mm', 'cm', 'm'],
|
||||
} as const
|
||||
|
||||
export type BaseUnit = Models['UnitLength_type']
|
||||
|
||||
export const baseUnitsUnion = Object.values(baseUnits).flatMap((v) => v)
|
||||
|
||||
export type Toggle = 'On' | 'Off'
|
||||
const toggleAsArray = ['On', 'Off'] as const
|
||||
|
||||
export type SettingsMachineContext = {
|
||||
baseUnit: BaseUnit
|
||||
cameraControls: CameraSystem
|
||||
defaultDirectory: string
|
||||
defaultProjectName: string
|
||||
onboardingStatus: string
|
||||
showDebugPanel: boolean
|
||||
textWrapping: Toggle
|
||||
theme: Themes
|
||||
unitSystem: UnitSystem
|
||||
}
|
||||
|
||||
export const initialSettings: SettingsMachineContext = {
|
||||
baseUnit: 'in' as BaseUnit,
|
||||
cameraControls: 'KittyCAD' as CameraSystem,
|
||||
defaultDirectory: '',
|
||||
defaultProjectName: DEFAULT_PROJECT_NAME,
|
||||
onboardingStatus: '',
|
||||
showDebugPanel: false,
|
||||
textWrapping: 'On' as Toggle,
|
||||
theme: Themes.System,
|
||||
unitSystem: UnitSystem.Imperial,
|
||||
}
|
||||
|
||||
function isEnumMember<T extends Record<string, unknown>>(v: unknown, e: T) {
|
||||
return Object.values(e).includes(v)
|
||||
}
|
||||
|
||||
const settingsValidators: Record<
|
||||
keyof SettingsMachineContext,
|
||||
(v: unknown) => boolean
|
||||
> = {
|
||||
baseUnit: (v) => baseUnitsUnion.includes(v as BaseUnit),
|
||||
cameraControls: (v) => cameraSystems.includes(v as CameraSystem),
|
||||
defaultDirectory: (v) => typeof v === 'string',
|
||||
defaultProjectName: (v) => typeof v === 'string',
|
||||
onboardingStatus: (v) => typeof v === 'string',
|
||||
showDebugPanel: (v) => typeof v === 'boolean',
|
||||
textWrapping: (v) => toggleAsArray.includes(v as Toggle),
|
||||
theme: (v) => isEnumMember(v, Themes),
|
||||
unitSystem: (v) => isEnumMember(v, UnitSystem),
|
||||
}
|
||||
|
||||
function removeInvalidSettingsKeys(s: Record<string, unknown>) {
|
||||
const validKeys = Object.keys(initialSettings)
|
||||
for (const key in s) {
|
||||
if (!validKeys.includes(key)) {
|
||||
console.warn(`Invalid key found in settings: ${key}`)
|
||||
delete s[key]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
export function validateSettings(s: Record<string, unknown>) {
|
||||
let settingsNoInvalidKeys = removeInvalidSettingsKeys({ ...s })
|
||||
let errors: (keyof SettingsMachineContext)[] = []
|
||||
for (const key in settingsNoInvalidKeys) {
|
||||
const k = key as keyof SettingsMachineContext
|
||||
if (!settingsValidators[k](settingsNoInvalidKeys[k])) {
|
||||
delete settingsNoInvalidKeys[k]
|
||||
errors.push(k)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
settings: settingsNoInvalidKeys as Partial<SettingsMachineContext>,
|
||||
errors,
|
||||
}
|
||||
}
|
@ -3,16 +3,12 @@ import {
|
||||
createDir,
|
||||
exists,
|
||||
readDir,
|
||||
readTextFile,
|
||||
writeTextFile,
|
||||
} from '@tauri-apps/api/fs'
|
||||
import { appConfigDir, documentDir, homeDir, sep } from '@tauri-apps/api/path'
|
||||
import { documentDir, homeDir, sep } from '@tauri-apps/api/path'
|
||||
import { isTauri } from './isTauri'
|
||||
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
||||
import { metadata } from 'tauri-plugin-fs-extra-api'
|
||||
import { settingsMachine } from 'machines/settingsMachine'
|
||||
import { ContextFrom } from 'xstate'
|
||||
import { SETTINGS_FILE_NAME } from 'lib/settings'
|
||||
|
||||
const PROJECT_FOLDER = 'zoo-modeling-app-projects'
|
||||
export const FILE_EXT = '.kcl'
|
||||
@ -21,101 +17,39 @@ const INDEX_IDENTIFIER = '$n' // $nn.. will pad the number with 0s
|
||||
export const MAX_PADDING = 7
|
||||
const RELEVANT_FILE_TYPES = ['kcl']
|
||||
|
||||
type PathWithPossibleError = {
|
||||
path: string | null
|
||||
error: Error | null
|
||||
}
|
||||
|
||||
export async function getInitialDefaultDir() {
|
||||
let dir
|
||||
try {
|
||||
dir = await documentDir()
|
||||
} catch (e) {
|
||||
dir = `${await homeDir()}Documents/` // for headless Linux (eg. Github Actions)
|
||||
}
|
||||
|
||||
return dir + PROJECT_FOLDER
|
||||
}
|
||||
|
||||
// Initializes the project directory and returns the path
|
||||
// with any Errors that occurred
|
||||
export async function initializeProjectDirectory(
|
||||
directory: string
|
||||
): Promise<PathWithPossibleError> {
|
||||
export async function initializeProjectDirectory(directory: string) {
|
||||
if (!isTauri()) {
|
||||
throw new Error(
|
||||
'initializeProjectDirectory() can only be called from a Tauri app'
|
||||
)
|
||||
}
|
||||
|
||||
let returnValue: PathWithPossibleError = {
|
||||
path: null,
|
||||
error: null,
|
||||
}
|
||||
|
||||
if (directory) {
|
||||
returnValue = await testAndCreateDir(directory, returnValue)
|
||||
const dirExists = await exists(directory)
|
||||
if (!dirExists) {
|
||||
await createDir(directory, { recursive: true })
|
||||
}
|
||||
return directory
|
||||
}
|
||||
|
||||
// If the directory from settings does not exist or could not be created,
|
||||
// use the default directory
|
||||
if (returnValue.path === null) {
|
||||
const INITIAL_DEFAULT_DIR = await getInitialDefaultDir()
|
||||
const defaultReturnValue = await testAndCreateDir(
|
||||
INITIAL_DEFAULT_DIR,
|
||||
returnValue,
|
||||
{
|
||||
exists: 'Error checking default directory.',
|
||||
create: 'Error creating default directory.',
|
||||
}
|
||||
)
|
||||
returnValue.path = defaultReturnValue.path
|
||||
returnValue.error =
|
||||
returnValue.error === null ? defaultReturnValue.error : returnValue.error
|
||||
let docDirectory: string
|
||||
try {
|
||||
docDirectory = await documentDir()
|
||||
} catch (e) {
|
||||
console.log('error', e)
|
||||
docDirectory = `${await homeDir()}Documents/` // for headless Linux (eg. Github Actions)
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
const INITIAL_DEFAULT_DIR = docDirectory + PROJECT_FOLDER
|
||||
|
||||
async function testAndCreateDir(
|
||||
directory: string,
|
||||
returnValue = {
|
||||
path: null,
|
||||
error: null,
|
||||
} as PathWithPossibleError,
|
||||
errorMessages = {
|
||||
exists:
|
||||
'Error checking directory at path from saved settings. Using default.',
|
||||
create:
|
||||
'Error creating directory at path from saved settings. Using default.',
|
||||
}
|
||||
): Promise<PathWithPossibleError> {
|
||||
const dirExists = await exists(directory).catch((e) => {
|
||||
console.error(`Error checking directory ${directory}. Original error:`, e)
|
||||
return new Error(errorMessages.exists)
|
||||
})
|
||||
const defaultDirExists = await exists(INITIAL_DEFAULT_DIR)
|
||||
|
||||
if (dirExists instanceof Error) {
|
||||
returnValue.error = dirExists
|
||||
} else if (dirExists && dirExists === true) {
|
||||
const newDirCreated = await createDir(directory, { recursive: true }).catch(
|
||||
(e) => {
|
||||
console.error(
|
||||
`Error creating directory ${directory}. Original error:`,
|
||||
e
|
||||
)
|
||||
return new Error(errorMessages.create)
|
||||
}
|
||||
)
|
||||
|
||||
if (newDirCreated instanceof Error) {
|
||||
returnValue.error = newDirCreated
|
||||
} else {
|
||||
returnValue.path = directory
|
||||
}
|
||||
if (!defaultDirExists) {
|
||||
await createDir(INITIAL_DEFAULT_DIR, { recursive: true })
|
||||
}
|
||||
|
||||
return returnValue
|
||||
return INITIAL_DEFAULT_DIR
|
||||
}
|
||||
|
||||
export function isProjectDirectory(fileOrDir: Partial<FileEntry>) {
|
||||
@ -366,44 +300,3 @@ function getPaddedIdentifierRegExp() {
|
||||
const escapedIdentifier = escapeRegExpChars(INDEX_IDENTIFIER)
|
||||
return new RegExp(`${escapedIdentifier}(${escapedIdentifier.slice(-1)}*)`)
|
||||
}
|
||||
|
||||
export async function getSettingsFilePath() {
|
||||
const dir = await appConfigDir()
|
||||
return dir + SETTINGS_FILE_NAME
|
||||
}
|
||||
|
||||
export async function writeToSettingsFile(
|
||||
settings: ContextFrom<typeof settingsMachine>
|
||||
) {
|
||||
return writeTextFile(
|
||||
await getSettingsFilePath(),
|
||||
JSON.stringify(settings, null, 2)
|
||||
)
|
||||
}
|
||||
|
||||
export async function readSettingsFile(): Promise<ContextFrom<
|
||||
typeof settingsMachine
|
||||
> | null> {
|
||||
const dir = await appConfigDir()
|
||||
const path = dir + SETTINGS_FILE_NAME
|
||||
const dirExists = await exists(dir)
|
||||
if (!dirExists) {
|
||||
await createDir(dir, { recursive: true })
|
||||
}
|
||||
|
||||
const settingsExist = dirExists ? await exists(path) : false
|
||||
|
||||
if (!settingsExist) {
|
||||
console.log(`Settings file does not exist at ${path}`)
|
||||
await writeToSettingsFile(settingsMachine.initialState.context)
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const settings = await readTextFile(path)
|
||||
return JSON.parse(settings)
|
||||
} catch (e) {
|
||||
console.error('Error reading settings file:', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -13,5 +13,4 @@ export type ProjectWithEntryPointMetadata = FileEntry & {
|
||||
export type HomeLoaderData = {
|
||||
projects: ProjectWithEntryPointMetadata[]
|
||||
newDefaultDirectory?: string
|
||||
error: Error | null
|
||||
}
|
||||
|
@ -1,43 +1,49 @@
|
||||
import { assign, createMachine } from 'xstate'
|
||||
import { Themes, getSystemTheme, setThemeClass } from '../lib/theme'
|
||||
import { CameraSystem } from 'lib/cameraControls'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { writeToSettingsFile } from 'lib/tauriFS'
|
||||
import {
|
||||
BaseUnit,
|
||||
DEFAULT_PROJECT_NAME,
|
||||
SETTINGS_PERSIST_KEY,
|
||||
SettingsMachineContext,
|
||||
Toggle,
|
||||
UnitSystem,
|
||||
initialSettings,
|
||||
} from 'lib/settings'
|
||||
import { Models } from '@kittycad/lib'
|
||||
|
||||
export const DEFAULT_PROJECT_NAME = 'project-$nnn'
|
||||
|
||||
export enum UnitSystem {
|
||||
Imperial = 'imperial',
|
||||
Metric = 'metric',
|
||||
}
|
||||
|
||||
export const baseUnits = {
|
||||
imperial: ['in', 'ft', 'yd'],
|
||||
metric: ['mm', 'cm', 'm'],
|
||||
} as const
|
||||
|
||||
export type BaseUnit = Models['UnitLength_type']
|
||||
|
||||
export const baseUnitsUnion = Object.values(baseUnits).flatMap((v) => v)
|
||||
|
||||
export type Toggle = 'On' | 'Off'
|
||||
|
||||
export const SETTINGS_PERSIST_KEY = 'SETTINGS_PERSIST_KEY'
|
||||
|
||||
export const settingsMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGUwBc0EsB2VYDpMIAbMAYlTQAIAVACzAFswBtABgF1FQAHAe1iYsfbNxAAPRAA42+AEwB2KQFYAzGznKAnADZli1QBoQAT2kBGKfm37lOned3nzqgL6vjlLLgJFSFdCoAETAAMwBDAFdiagAFACc+ACswAGNqADlw5nYuJBB+QWFRfMkEABY5fDYa2rra83LjMwQdLWV8BXLyuxlVLU1Ld090bzxCEnJKYLComODMeLS0PniTXLFCoUwRMTK7fC1zNql7NgUjtnKjU0RlBSqpLVUVPVUda60tYZAvHHG-FNAgBVbBCKjIEywNBMDb5LbFPaILqdfRSORsS4qcxXZqIHqyK6qY4XOxsGTKco-P4+Cb+aYAIXCsDAVFBQjhvAE212pWkskUKnUml0+gUNxaqkU+EccnKF1UCnucnMcjcHl+o3+vkmZBofCgUFIMwARpEoFRYuFsGBiJyCtzEXzWrJlGxlKdVFKvfY1XiEBjyvhVOVzBdzu13pYFNStbTAQFqAB5bAmvjheIQf4QtDhNCRWD2hE7EqgfayHTEh7lHQNSxSf1Scz4cpHHFyFVujTKczuDXYPgQOBiGl4TaOktIhAAWg6X3nC4Xp39050sYw2rpYHHRUnztVhPJqmUlIGbEriv9WhrLZ6uibHcqUr7riAA */
|
||||
id: 'Settings',
|
||||
predictableActionArguments: true,
|
||||
context: { ...initialSettings },
|
||||
context: {
|
||||
baseUnit: 'in' as BaseUnit,
|
||||
cameraControls: 'KittyCAD' as CameraSystem,
|
||||
defaultDirectory: '',
|
||||
defaultProjectName: DEFAULT_PROJECT_NAME,
|
||||
onboardingStatus: '',
|
||||
showDebugPanel: false,
|
||||
textWrapping: 'On' as Toggle,
|
||||
theme: Themes.System,
|
||||
unitSystem: UnitSystem.Imperial,
|
||||
},
|
||||
initial: 'idle',
|
||||
states: {
|
||||
idle: {
|
||||
entry: ['setThemeClass'],
|
||||
on: {
|
||||
'Set All Settings': {
|
||||
actions: [
|
||||
assign((context, event) => {
|
||||
return {
|
||||
...context,
|
||||
...event.data,
|
||||
}
|
||||
}),
|
||||
'persistSettings',
|
||||
'setThemeClass',
|
||||
],
|
||||
target: 'idle',
|
||||
internal: true,
|
||||
},
|
||||
'Set Base Unit': {
|
||||
actions: [
|
||||
assign({
|
||||
@ -151,7 +157,6 @@ export const settingsMachine = createMachine(
|
||||
tsTypes: {} as import('./settingsMachine.typegen').Typegen0,
|
||||
schema: {
|
||||
events: {} as
|
||||
| { type: 'Set All Settings'; data: Partial<SettingsMachineContext> }
|
||||
| { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } }
|
||||
| {
|
||||
type: 'Set Camera Controls'
|
||||
@ -175,11 +180,6 @@ export const settingsMachine = createMachine(
|
||||
{
|
||||
actions: {
|
||||
persistSettings: (context) => {
|
||||
if (isTauri()) {
|
||||
writeToSettingsFile(context).catch((err) => {
|
||||
console.error('Error writing settings:', err)
|
||||
})
|
||||
}
|
||||
try {
|
||||
localStorage.setItem(SETTINGS_PERSIST_KEY, JSON.stringify(context))
|
||||
} catch (e) {
|
||||
|
@ -29,9 +29,9 @@ import {
|
||||
getSortIcon,
|
||||
} from '../lib/sorting'
|
||||
import useStateMachineCommands from '../hooks/useStateMachineCommands'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { DEFAULT_PROJECT_NAME } from 'lib/settings'
|
||||
import { DEFAULT_PROJECT_NAME } from 'machines/settingsMachine'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
import { homeCommandBarConfig } from 'lib/commandBarConfigs/homeCommandConfig'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
@ -42,17 +42,14 @@ import { isTauri } from 'lib/isTauri'
|
||||
const Home = () => {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const navigate = useNavigate()
|
||||
const {
|
||||
projects: loadedProjects,
|
||||
newDefaultDirectory,
|
||||
error,
|
||||
} = useLoaderData() as HomeLoaderData
|
||||
const { projects: loadedProjects, newDefaultDirectory } =
|
||||
useLoaderData() as HomeLoaderData
|
||||
const {
|
||||
settings: {
|
||||
context: { defaultDirectory, defaultProjectName },
|
||||
send: sendToSettings,
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
|
||||
// Set the default directory if it's been updated
|
||||
// during the loading of the home page. This is wrapped
|
||||
@ -60,17 +57,11 @@ const Home = () => {
|
||||
useEffect(() => {
|
||||
if (newDefaultDirectory) {
|
||||
sendToSettings({
|
||||
type: 'Set All Settings',
|
||||
type: 'Set Default Directory',
|
||||
data: { defaultDirectory: newDefaultDirectory },
|
||||
})
|
||||
}
|
||||
|
||||
// Toast any errors that occurred during the loading process
|
||||
if (error) {
|
||||
toast.error(error.message)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useHotkeys(
|
||||
isTauri() ? 'mod+,' : 'shift+mod+,',
|
||||
() => navigate(paths.HOME + paths.SETTINGS),
|
||||
|
@ -2,7 +2,7 @@ import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { SettingsSection } from 'routes/Settings'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import {
|
||||
CameraSystem,
|
||||
cameraMouseDragGuards,
|
||||
@ -22,7 +22,7 @@ export default function Units() {
|
||||
context: { cameraControls },
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
useNextClick,
|
||||
} from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import {
|
||||
@ -31,7 +31,7 @@ function OnboardingWithNewFile() {
|
||||
settings: {
|
||||
context: { defaultDirectory },
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
|
||||
async function createAndOpenNewProject() {
|
||||
const projects = await getProjectsInDir(defaultDirectory)
|
||||
@ -111,7 +111,7 @@ export default function Introduction() {
|
||||
context: { theme },
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
const getLogoTheme = () =>
|
||||
theme === Themes.Light ||
|
||||
(theme === Themes.System && getSystemTheme() === Themes.Light)
|
||||
|
@ -3,7 +3,7 @@ import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useStore } from '../../useStore'
|
||||
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
|
||||
export default function ParametricModeling() {
|
||||
const { buttonDownInStream } = useStore((s) => ({
|
||||
@ -13,7 +13,7 @@ export default function ParametricModeling() {
|
||||
settings: {
|
||||
context: { theme },
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
const getImageTheme = () =>
|
||||
theme === Themes.Light ||
|
||||
(theme === Themes.System && getSystemTheme() === Themes.Light)
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||
import { BaseUnit, baseUnits, UnitSystem } from 'lib/settings'
|
||||
import { BaseUnit, baseUnits } from '../../machines/settingsMachine'
|
||||
import { ActionButton } from '../../components/ActionButton'
|
||||
import { SettingsSection } from '../Settings'
|
||||
import { Toggle } from '../../components/Toggle/Toggle'
|
||||
import { useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { UnitSystem } from 'machines/settingsMachine'
|
||||
|
||||
export default function Units() {
|
||||
const dismiss = useDismiss()
|
||||
@ -15,7 +16,7 @@ export default function Units() {
|
||||
send,
|
||||
context: { unitSystem, baseUnit },
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
|
||||
return (
|
||||
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
||||
|
@ -5,7 +5,7 @@ import Camera from './Camera'
|
||||
import Sketching from './Sketching'
|
||||
import { useCallback } from 'react'
|
||||
import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import Streaming from './Streaming'
|
||||
import CodeEditor from './CodeEditor'
|
||||
import ParametricModeling from './ParametricModeling'
|
||||
@ -78,7 +78,7 @@ export function useNextClick(newStatus: string) {
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const {
|
||||
settings: { send },
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
return useCallback(() => {
|
||||
@ -94,7 +94,7 @@ export function useDismiss() {
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const {
|
||||
settings: { send },
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
return useCallback(() => {
|
||||
|
@ -5,38 +5,31 @@ import { open } from '@tauri-apps/api/dialog'
|
||||
import {
|
||||
BaseUnit,
|
||||
DEFAULT_PROJECT_NAME,
|
||||
SETTINGS_PERSIST_KEY,
|
||||
baseUnits,
|
||||
initialSettings,
|
||||
UnitSystem,
|
||||
} from 'lib/settings'
|
||||
} from '../machines/settingsMachine'
|
||||
import { Toggle } from '../components/Toggle/Toggle'
|
||||
import { useLocation, useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { paths } from 'lib/paths'
|
||||
import { Themes } from '../lib/theme'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import {
|
||||
CameraSystem,
|
||||
cameraSystems,
|
||||
cameraMouseDragGuards,
|
||||
} from 'lib/cameraControls'
|
||||
import { UnitSystem } from 'machines/settingsMachine'
|
||||
import { useDotDotSlash } from 'hooks/useDotDotSlash'
|
||||
import {
|
||||
createNewProject,
|
||||
getNextProjectIndex,
|
||||
getProjectsInDir,
|
||||
getSettingsFilePath,
|
||||
initializeProjectDirectory,
|
||||
interpolateProjectNameWithIndex,
|
||||
} from 'lib/tauriFS'
|
||||
import { ONBOARDING_PROJECT_NAME } from './Onboarding'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
export const Settings = () => {
|
||||
const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
|
||||
@ -62,14 +55,11 @@ export const Settings = () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
|
||||
async function handleDirectorySelection() {
|
||||
// the `recursive` property added following
|
||||
// this advice for permissions: https://github.com/tauri-apps/tauri/issues/4851#issuecomment-1210711455
|
||||
const newDirectory = await open({
|
||||
directory: true,
|
||||
recursive: true,
|
||||
defaultPath: defaultDirectory || paths.INDEX,
|
||||
title: 'Choose a new default directory',
|
||||
})
|
||||
@ -316,59 +306,6 @@ export const Settings = () => {
|
||||
Replay Onboarding
|
||||
</ActionButton>
|
||||
</SettingsSection>
|
||||
<p className="font-mono my-6 leading-loose">
|
||||
Your settings are saved in{' '}
|
||||
{isTauri()
|
||||
? 'a file in the app data folder for your OS.'
|
||||
: "your browser's local storage."}{' '}
|
||||
{isTauri() ? (
|
||||
<span className="flex gap-4 flex-wrap items-center">
|
||||
<button
|
||||
onClick={async () =>
|
||||
void invoke('show_in_folder', {
|
||||
path: await getSettingsFilePath(),
|
||||
})
|
||||
}
|
||||
className="text-base"
|
||||
>
|
||||
Show settings.json in folder
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
// We have to re-call initializeProjectDirectory
|
||||
// since we can't set that in the settings machine's
|
||||
// initial context due to it being async
|
||||
send({
|
||||
type: 'Set All Settings',
|
||||
data: {
|
||||
...initialSettings,
|
||||
defaultDirectory:
|
||||
(await initializeProjectDirectory('')).path ?? '',
|
||||
},
|
||||
})
|
||||
toast.success('Settings restored to default')
|
||||
}}
|
||||
className="text-base"
|
||||
>
|
||||
Restore default settings
|
||||
</button>
|
||||
</span>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => {
|
||||
localStorage.removeItem(SETTINGS_PERSIST_KEY)
|
||||
send({
|
||||
type: 'Set All Settings',
|
||||
data: initialSettings,
|
||||
})
|
||||
toast.success('Settings restored to default')
|
||||
}}
|
||||
className="text-base"
|
||||
>
|
||||
Restore default settings
|
||||
</button>
|
||||
)}
|
||||
</p>
|
||||
<p className="mt-24 text-sm font-mono">
|
||||
{/* This uses a Vite plugin, set in vite.config.ts
|
||||
to inject the version from package.json */}
|
||||
|
@ -4,7 +4,7 @@ import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { VITE_KC_SITE_BASE_URL, VITE_KC_API_BASE_URL } from '../env'
|
||||
import { Themes, getSystemTheme } from '../lib/theme'
|
||||
import { paths } from 'lib/paths'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
|
||||
const SignIn = () => {
|
||||
@ -20,7 +20,7 @@ const SignIn = () => {
|
||||
context: { theme },
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
} = useGlobalStateContext()
|
||||
|
||||
const signInTauri = async () => {
|
||||
// We want to invoke our command to login via device auth.
|
||||
|
Reference in New Issue
Block a user