This reverts commit 602e7afef6
.
This commit is contained in:
@ -3,7 +3,6 @@ import { secrets } from './secrets'
|
|||||||
import { getUtils } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import waitOn from 'wait-on'
|
import waitOn from 'wait-on'
|
||||||
import { Themes } from '../../src/lib/theme'
|
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
|
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, %)`)
|
|> 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
|
// Onboarding tests
|
||||||
test('Onboarding redirects and code updating', async ({ page, context }) => {
|
test('Onboarding redirects and code updating', async ({ page, context }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
|
@ -143,25 +143,6 @@ async fn get_user(
|
|||||||
Ok(user_info)
|
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() {
|
fn main() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.setup(|_app| {
|
.setup(|_app| {
|
||||||
@ -178,8 +159,7 @@ fn main() {
|
|||||||
get_user,
|
get_user,
|
||||||
login,
|
login,
|
||||||
read_toml,
|
read_toml,
|
||||||
read_txt_file,
|
read_txt_file
|
||||||
show_in_folder,
|
|
||||||
])
|
])
|
||||||
.plugin(tauri_plugin_fs_extra::init())
|
.plugin(tauri_plugin_fs_extra::init())
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
"fs": {
|
"fs": {
|
||||||
"scope": [
|
"scope": [
|
||||||
"$HOME/**/*",
|
"$HOME/**/*",
|
||||||
"$APPDATA/**/*",
|
"$APPDATA/**/*"
|
||||||
"$DOCUMENT/**/*"
|
|
||||||
],
|
],
|
||||||
"all": true
|
"all": true
|
||||||
},
|
},
|
||||||
@ -61,7 +60,7 @@
|
|||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
"identifier": "zoo-modeling-app",
|
"identifier": "io.kittycad.modeling-app",
|
||||||
"longDescription": "",
|
"longDescription": "",
|
||||||
"macOS": {
|
"macOS": {
|
||||||
"entitlements": null,
|
"entitlements": null,
|
||||||
|
@ -22,7 +22,7 @@ import { getNormalisedCoordinates } from './lib/utils'
|
|||||||
import { useLoaderData, useNavigate } from 'react-router-dom'
|
import { useLoaderData, useNavigate } from 'react-router-dom'
|
||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { CodeMenu } from 'components/CodeMenu'
|
import { CodeMenu } from 'components/CodeMenu'
|
||||||
import { TextEditor } from 'components/TextEditor'
|
import { TextEditor } from 'components/TextEditor'
|
||||||
@ -53,7 +53,7 @@ export function App() {
|
|||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useGlobalStateContext()
|
||||||
const { showDebugPanel, onboardingStatus, theme } = settings?.context || {}
|
const { showDebugPanel, onboardingStatus, theme } = settings?.context || {}
|
||||||
const { state, send } = useModelingContext()
|
const { state, send } = useModelingContext()
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import Loading from './components/Loading'
|
import Loading from './components/Loading'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
|
||||||
// Wrapper around protected routes, used in src/Router.tsx
|
// Wrapper around protected routes, used in src/Router.tsx
|
||||||
export const Auth = ({ children }: React.PropsWithChildren) => {
|
export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||||
const { auth } = useSettingsAuthContext()
|
const { auth } = useGlobalStateContext()
|
||||||
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
|
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
|
||||||
|
|
||||||
return isLoggingIn ? (
|
return isLoggingIn ? (
|
||||||
|
@ -29,9 +29,11 @@ import {
|
|||||||
import { metadata } from 'tauri-plugin-fs-extra-api'
|
import { metadata } from 'tauri-plugin-fs-extra-api'
|
||||||
import DownloadAppBanner from './components/DownloadAppBanner'
|
import DownloadAppBanner from './components/DownloadAppBanner'
|
||||||
import { WasmErrBanner } from './components/WasmErrBanner'
|
import { WasmErrBanner } from './components/WasmErrBanner'
|
||||||
import { SettingsAuthStateProvider } from './components/SettingsAuthStateProvider'
|
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
||||||
import { settingsMachine } from './machines/settingsMachine'
|
import {
|
||||||
import { SETTINGS_PERSIST_KEY } from 'lib/settings'
|
SETTINGS_PERSIST_KEY,
|
||||||
|
settingsMachine,
|
||||||
|
} from './machines/settingsMachine'
|
||||||
import { ContextFrom } from 'xstate'
|
import { ContextFrom } from 'xstate'
|
||||||
import CommandBarProvider from 'components/CommandBar/CommandBar'
|
import CommandBarProvider from 'components/CommandBar/CommandBar'
|
||||||
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
||||||
@ -89,9 +91,7 @@ const addGlobalContextToElements = (
|
|||||||
...route,
|
...route,
|
||||||
element: (
|
element: (
|
||||||
<CommandBarProvider>
|
<CommandBarProvider>
|
||||||
<SettingsAuthStateProvider>
|
<GlobalStateProvider>{route.element}</GlobalStateProvider>
|
||||||
{route.element}
|
|
||||||
</SettingsAuthStateProvider>
|
|
||||||
</CommandBarProvider>
|
</CommandBarProvider>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -229,42 +229,32 @@ const router = createBrowserRouter(
|
|||||||
const projectDir = await initializeProjectDirectory(
|
const projectDir = await initializeProjectDirectory(
|
||||||
persistedSettings.defaultDirectory || ''
|
persistedSettings.defaultDirectory || ''
|
||||||
)
|
)
|
||||||
|
|
||||||
let newDefaultDirectory: string | undefined = undefined
|
let newDefaultDirectory: string | undefined = undefined
|
||||||
if (projectDir.path) {
|
if (projectDir !== persistedSettings.defaultDirectory) {
|
||||||
if (projectDir.path !== persistedSettings.defaultDirectory) {
|
localStorage.setItem(
|
||||||
localStorage.setItem(
|
SETTINGS_PERSIST_KEY,
|
||||||
SETTINGS_PERSIST_KEY,
|
JSON.stringify({
|
||||||
JSON.stringify({
|
...persistedSettings,
|
||||||
...persistedSettings,
|
defaultDirectory: projectDir,
|
||||||
defaultDirectory: projectDir,
|
})
|
||||||
})
|
|
||||||
)
|
|
||||||
newDefaultDirectory = projectDir.path
|
|
||||||
}
|
|
||||||
const projectsNoMeta = (await readDir(projectDir.path)).filter(
|
|
||||||
isProjectDirectory
|
|
||||||
)
|
|
||||||
const projects = await Promise.all(
|
|
||||||
projectsNoMeta.map(async (p: FileEntry) => ({
|
|
||||||
entrypointMetadata: await metadata(
|
|
||||||
p.path + sep + PROJECT_ENTRYPOINT
|
|
||||||
),
|
|
||||||
...p,
|
|
||||||
}))
|
|
||||||
)
|
)
|
||||||
|
newDefaultDirectory = projectDir
|
||||||
|
}
|
||||||
|
const projectsNoMeta = (await readDir(projectDir)).filter(
|
||||||
|
isProjectDirectory
|
||||||
|
)
|
||||||
|
const projects = await Promise.all(
|
||||||
|
projectsNoMeta.map(async (p: FileEntry) => ({
|
||||||
|
entrypointMetadata: await metadata(
|
||||||
|
p.path + sep + PROJECT_ENTRYPOINT
|
||||||
|
),
|
||||||
|
...p,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projects,
|
projects,
|
||||||
newDefaultDirectory,
|
newDefaultDirectory,
|
||||||
error: projectDir.error,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
projects: [],
|
|
||||||
newDefaultDirectory,
|
|
||||||
error: projectDir.error,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
@ -2,7 +2,7 @@ import { useRef, useEffect, useState } from 'react'
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
|
||||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import {
|
import {
|
||||||
DEBUG_SHOW_BOTH_SCENES,
|
DEBUG_SHOW_BOTH_SCENES,
|
||||||
@ -38,7 +38,7 @@ export const ClientSideScene = ({
|
|||||||
cameraControls,
|
cameraControls,
|
||||||
}: {
|
}: {
|
||||||
cameraControls: ReturnType<
|
cameraControls: ReturnType<
|
||||||
typeof useSettingsAuthContext
|
typeof useGlobalStateContext
|
||||||
>['settings']['context']['cameraControls']
|
>['settings']['context']['cameraControls']
|
||||||
}) => {
|
}) => {
|
||||||
const canvasRef = useRef<HTMLDivElement>(null)
|
const canvasRef = useRef<HTMLDivElement>(null)
|
||||||
|
@ -2,7 +2,7 @@ import { Toolbar } from '../Toolbar'
|
|||||||
import UserSidebarMenu from './UserSidebarMenu'
|
import UserSidebarMenu from './UserSidebarMenu'
|
||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import styles from './AppHeader.module.css'
|
import styles from './AppHeader.module.css'
|
||||||
import { NetworkHealthIndicator } from './NetworkHealthIndicator'
|
import { NetworkHealthIndicator } from './NetworkHealthIndicator'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
@ -25,7 +25,7 @@ export const AppHeader = ({
|
|||||||
}: AppHeaderProps) => {
|
}: AppHeaderProps) => {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
const { auth } = useSettingsAuthContext()
|
const { auth } = useGlobalStateContext()
|
||||||
const user = auth?.context?.user
|
const user = auth?.context?.user
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -6,7 +6,7 @@ import React from 'react'
|
|||||||
import { useFormik } from 'formik'
|
import { useFormik } from 'formik'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
|
||||||
type OutputFormat = Models['OutputFormat_type']
|
type OutputFormat = Models['OutputFormat_type']
|
||||||
type OutputTypeKey = OutputFormat['type']
|
type OutputTypeKey = OutputFormat['type']
|
||||||
@ -29,7 +29,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
|||||||
context: { baseUnit },
|
context: { baseUnit },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
const defaultType = 'gltf'
|
const defaultType = 'gltf'
|
||||||
const [type, setType] = React.useState<OutputTypeKey>(defaultType)
|
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 withBaseUrl from '../lib/withBaseURL'
|
||||||
import React, { createContext, useEffect, useRef } from 'react'
|
import React, { createContext, useEffect, useRef } from 'react'
|
||||||
import useStateMachineCommands from '../hooks/useStateMachineCommands'
|
import useStateMachineCommands from '../hooks/useStateMachineCommands'
|
||||||
import { settingsMachine } from 'machines/settingsMachine'
|
import { SETTINGS_PERSIST_KEY, settingsMachine } from 'machines/settingsMachine'
|
||||||
import {
|
|
||||||
initialSettings,
|
|
||||||
SETTINGS_PERSIST_KEY,
|
|
||||||
validateSettings,
|
|
||||||
} from 'lib/settings'
|
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import { setThemeClass, Themes } from 'lib/theme'
|
import { setThemeClass, Themes } from 'lib/theme'
|
||||||
import {
|
import {
|
||||||
@ -23,7 +18,6 @@ import {
|
|||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
import { settingsCommandBarConfig } from 'lib/commandBarConfigs/settingsCommandConfig'
|
import { settingsCommandBarConfig } from 'lib/commandBarConfigs/settingsCommandConfig'
|
||||||
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
||||||
import { initializeProjectDirectory, readSettingsFile } from 'lib/tauriFS'
|
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -31,14 +25,14 @@ type MachineContext<T extends AnyStateMachine> = {
|
|||||||
send: Prop<InterpreterFrom<T>, 'send'>
|
send: Prop<InterpreterFrom<T>, 'send'>
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingsAuthContext = {
|
type GlobalContext = {
|
||||||
auth: MachineContext<typeof authMachine>
|
auth: MachineContext<typeof authMachine>
|
||||||
settings: MachineContext<typeof settingsMachine>
|
settings: MachineContext<typeof settingsMachine>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsAuthStateContext = createContext({} as SettingsAuthContext)
|
export const GlobalStateContext = createContext({} as GlobalContext)
|
||||||
|
|
||||||
export const SettingsAuthStateProvider = ({
|
export const GlobalStateProvider = ({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
@ -46,17 +40,14 @@ export const SettingsAuthStateProvider = ({
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
// Settings machine setup
|
// Settings machine setup
|
||||||
// Load settings from local storage
|
|
||||||
// and validate them
|
|
||||||
const retrievedSettings = useRef(
|
const retrievedSettings = useRef(
|
||||||
validateSettings(
|
localStorage?.getItem(SETTINGS_PERSIST_KEY) || '{}'
|
||||||
JSON.parse(localStorage?.getItem(SETTINGS_PERSIST_KEY) || '{}')
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
const persistedSettings = Object.assign(
|
const persistedSettings = Object.assign(
|
||||||
{},
|
settingsMachine.initialState.context,
|
||||||
initialSettings,
|
JSON.parse(retrievedSettings.current) as Partial<
|
||||||
retrievedSettings.current.settings
|
(typeof settingsMachine)['context']
|
||||||
|
>
|
||||||
)
|
)
|
||||||
|
|
||||||
const [settingsState, settingsSend] = useMachine(settingsMachine, {
|
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({
|
useStateMachineCommands({
|
||||||
machineId: 'settings',
|
machineId: 'settings',
|
||||||
state: settingsState,
|
state: settingsState,
|
||||||
@ -197,7 +119,7 @@ export const SettingsAuthStateProvider = ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsAuthStateContext.Provider
|
<GlobalStateContext.Provider
|
||||||
value={{
|
value={{
|
||||||
auth: {
|
auth: {
|
||||||
state: authState,
|
state: authState,
|
||||||
@ -212,11 +134,11 @@ export const SettingsAuthStateProvider = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</SettingsAuthStateContext.Provider>
|
</GlobalStateContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SettingsAuthStateProvider
|
export default GlobalStateProvider
|
||||||
|
|
||||||
export function logout() {
|
export function logout() {
|
||||||
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from 'xstate'
|
} from 'xstate'
|
||||||
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
|
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
|
||||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||||
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
import { kclManager, useKclContext } from 'lang/KclSingleton'
|
||||||
@ -53,7 +53,7 @@ export const ModelingMachineProvider = ({
|
|||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const { auth } = useSettingsAuthContext()
|
const { auth } = useGlobalStateContext()
|
||||||
const { code } = useKclContext()
|
const { code } = useKclContext()
|
||||||
const token = auth?.context?.token
|
const token = auth?.context?.token
|
||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import { SettingsAuthStateProvider } from './SettingsAuthStateProvider'
|
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||||
import CommandBarProvider from './CommandBar/CommandBar'
|
import CommandBarProvider from './CommandBar/CommandBar'
|
||||||
import {
|
import {
|
||||||
NETWORK_HEALTH_TEXT,
|
NETWORK_HEALTH_TEXT,
|
||||||
@ -13,7 +13,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
|
|||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<CommandBarProvider>
|
<CommandBarProvider>
|
||||||
<SettingsAuthStateProvider>{children}</SettingsAuthStateProvider>
|
<GlobalStateProvider>{children}</GlobalStateProvider>
|
||||||
</CommandBarProvider>
|
</CommandBarProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
|||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
||||||
import { SettingsAuthStateProvider } from './SettingsAuthStateProvider'
|
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||||
import CommandBarProvider from './CommandBar/CommandBar'
|
import CommandBarProvider from './CommandBar/CommandBar'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
|
|
||||||
@ -42,9 +42,9 @@ describe('ProjectSidebarMenu tests', () => {
|
|||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<CommandBarProvider>
|
<CommandBarProvider>
|
||||||
<SettingsAuthStateProvider>
|
<GlobalStateProvider>
|
||||||
<ProjectSidebarMenu project={projectWellFormed} />
|
<ProjectSidebarMenu project={projectWellFormed} />
|
||||||
</SettingsAuthStateProvider>
|
</GlobalStateProvider>
|
||||||
</CommandBarProvider>
|
</CommandBarProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
@ -63,9 +63,9 @@ describe('ProjectSidebarMenu tests', () => {
|
|||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<CommandBarProvider>
|
<CommandBarProvider>
|
||||||
<SettingsAuthStateProvider>
|
<GlobalStateProvider>
|
||||||
<ProjectSidebarMenu />
|
<ProjectSidebarMenu />
|
||||||
</SettingsAuthStateProvider>
|
</GlobalStateProvider>
|
||||||
</CommandBarProvider>
|
</CommandBarProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
@ -79,12 +79,12 @@ describe('ProjectSidebarMenu tests', () => {
|
|||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<CommandBarProvider>
|
<CommandBarProvider>
|
||||||
<SettingsAuthStateProvider>
|
<GlobalStateProvider>
|
||||||
<ProjectSidebarMenu
|
<ProjectSidebarMenu
|
||||||
project={projectWellFormed}
|
project={projectWellFormed}
|
||||||
renderAsLink={true}
|
renderAsLink={true}
|
||||||
/>
|
/>
|
||||||
</SettingsAuthStateProvider>
|
</GlobalStateProvider>
|
||||||
</CommandBarProvider>
|
</CommandBarProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
@ -10,7 +10,7 @@ import { useStore } from '../useStore'
|
|||||||
import { getNormalisedCoordinates, throttle } from '../lib/utils'
|
import { getNormalisedCoordinates, throttle } from '../lib/utils'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
@ -34,7 +34,7 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
setDidDragInStream: s.setDidDragInStream,
|
setDidDragInStream: s.setDidDragInStream,
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
}))
|
}))
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useGlobalStateContext()
|
||||||
const cameraControls = settings?.context?.cameraControls
|
const cameraControls = settings?.context?.cameraControls
|
||||||
const { state } = useModelingContext()
|
const { state } = useModelingContext()
|
||||||
const { isExecuting } = useKclContext()
|
const { isExecuting } = useKclContext()
|
||||||
|
@ -8,7 +8,7 @@ import Server from '../editor/lsp/server'
|
|||||||
import Client from '../editor/lsp/client'
|
import Client from '../editor/lsp/client'
|
||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||||
import { Themes } from 'lib/theme'
|
import { Themes } from 'lib/theme'
|
||||||
import { useMemo, useRef } from 'react'
|
import { useMemo, useRef } from 'react'
|
||||||
@ -72,7 +72,7 @@ export const TextEditor = ({
|
|||||||
} = useModelingContext()
|
} = useModelingContext()
|
||||||
|
|
||||||
const { settings: { context: { textWrapping } = {} } = {}, auth } =
|
const { settings: { context: { textWrapping } = {} } = {}, auth } =
|
||||||
useSettingsAuthContext()
|
useGlobalStateContext()
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
const { enable: convertEnabled, handleClick: convertCallback } =
|
const { enable: convertEnabled, handleClick: convertCallback } =
|
||||||
useConvertToVariable()
|
useConvertToVariable()
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
createRoutesFromElements,
|
createRoutesFromElements,
|
||||||
} from 'react-router-dom'
|
} from 'react-router-dom'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { SettingsAuthStateProvider } from './SettingsAuthStateProvider'
|
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||||
import CommandBarProvider from './CommandBar/CommandBar'
|
import CommandBarProvider from './CommandBar/CommandBar'
|
||||||
|
|
||||||
type User = Models['User_type']
|
type User = Models['User_type']
|
||||||
@ -107,7 +107,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
|
|||||||
path="/file/:id"
|
path="/file/:id"
|
||||||
element={
|
element={
|
||||||
<CommandBarProvider>
|
<CommandBarProvider>
|
||||||
<SettingsAuthStateProvider>{children}</SettingsAuthStateProvider>
|
<GlobalStateProvider>{children}</GlobalStateProvider>
|
||||||
</CommandBarProvider>
|
</CommandBarProvider>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -6,7 +6,7 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
|||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
|
|
||||||
type User = Models['User_type']
|
type User = Models['User_type']
|
||||||
@ -17,7 +17,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
const displayedName = getDisplayName(user)
|
const displayedName = getDisplayName(user)
|
||||||
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const send = useSettingsAuthContext()?.auth?.send
|
const send = useGlobalStateContext()?.auth?.send
|
||||||
|
|
||||||
// Fallback logic for displaying user's "name":
|
// Fallback logic for displaying user's "name":
|
||||||
// 1. user.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 { CommandSetConfig } from '../commandTypes'
|
||||||
import { BaseUnit, Toggle, UnitSystem, baseUnitsUnion } from 'lib/settings'
|
import {
|
||||||
import { settingsMachine } from 'machines/settingsMachine'
|
BaseUnit,
|
||||||
|
Toggle,
|
||||||
|
UnitSystem,
|
||||||
|
baseUnitsUnion,
|
||||||
|
settingsMachine,
|
||||||
|
} from 'machines/settingsMachine'
|
||||||
import { CameraSystem, cameraSystems } from '../cameraControls'
|
import { CameraSystem, cameraSystems } from '../cameraControls'
|
||||||
import { Themes } from '../theme'
|
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,
|
createDir,
|
||||||
exists,
|
exists,
|
||||||
readDir,
|
readDir,
|
||||||
readTextFile,
|
|
||||||
writeTextFile,
|
writeTextFile,
|
||||||
} from '@tauri-apps/api/fs'
|
} 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 { isTauri } from './isTauri'
|
||||||
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
||||||
import { metadata } from 'tauri-plugin-fs-extra-api'
|
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'
|
const PROJECT_FOLDER = 'zoo-modeling-app-projects'
|
||||||
export const FILE_EXT = '.kcl'
|
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
|
export const MAX_PADDING = 7
|
||||||
const RELEVANT_FILE_TYPES = ['kcl']
|
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
|
// Initializes the project directory and returns the path
|
||||||
// with any Errors that occurred
|
export async function initializeProjectDirectory(directory: string) {
|
||||||
export async function initializeProjectDirectory(
|
|
||||||
directory: string
|
|
||||||
): Promise<PathWithPossibleError> {
|
|
||||||
if (!isTauri()) {
|
if (!isTauri()) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'initializeProjectDirectory() can only be called from a Tauri app'
|
'initializeProjectDirectory() can only be called from a Tauri app'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let returnValue: PathWithPossibleError = {
|
|
||||||
path: null,
|
|
||||||
error: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (directory) {
|
if (directory) {
|
||||||
returnValue = await testAndCreateDir(directory, returnValue)
|
const dirExists = await exists(directory)
|
||||||
}
|
if (!dirExists) {
|
||||||
|
await createDir(directory, { recursive: true })
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
return directory
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnValue
|
let docDirectory: string
|
||||||
|
try {
|
||||||
|
docDirectory = await documentDir()
|
||||||
|
} catch (e) {
|
||||||
|
console.log('error', e)
|
||||||
|
docDirectory = `${await homeDir()}Documents/` // for headless Linux (eg. Github Actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
const INITIAL_DEFAULT_DIR = docDirectory + PROJECT_FOLDER
|
||||||
|
|
||||||
|
const defaultDirExists = await exists(INITIAL_DEFAULT_DIR)
|
||||||
|
|
||||||
|
if (!defaultDirExists) {
|
||||||
|
await createDir(INITIAL_DEFAULT_DIR, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
return INITIAL_DEFAULT_DIR
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isProjectDirectory(fileOrDir: Partial<FileEntry>) {
|
export function isProjectDirectory(fileOrDir: Partial<FileEntry>) {
|
||||||
@ -366,44 +300,3 @@ function getPaddedIdentifierRegExp() {
|
|||||||
const escapedIdentifier = escapeRegExpChars(INDEX_IDENTIFIER)
|
const escapedIdentifier = escapeRegExpChars(INDEX_IDENTIFIER)
|
||||||
return new RegExp(`${escapedIdentifier}(${escapedIdentifier.slice(-1)}*)`)
|
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 = {
|
export type HomeLoaderData = {
|
||||||
projects: ProjectWithEntryPointMetadata[]
|
projects: ProjectWithEntryPointMetadata[]
|
||||||
newDefaultDirectory?: string
|
newDefaultDirectory?: string
|
||||||
error: Error | null
|
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,49 @@
|
|||||||
import { assign, createMachine } from 'xstate'
|
import { assign, createMachine } from 'xstate'
|
||||||
import { Themes, getSystemTheme, setThemeClass } from '../lib/theme'
|
import { Themes, getSystemTheme, setThemeClass } from '../lib/theme'
|
||||||
import { CameraSystem } from 'lib/cameraControls'
|
import { CameraSystem } from 'lib/cameraControls'
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { Models } from '@kittycad/lib'
|
||||||
import { writeToSettingsFile } from 'lib/tauriFS'
|
|
||||||
import {
|
export const DEFAULT_PROJECT_NAME = 'project-$nnn'
|
||||||
BaseUnit,
|
|
||||||
DEFAULT_PROJECT_NAME,
|
export enum UnitSystem {
|
||||||
SETTINGS_PERSIST_KEY,
|
Imperial = 'imperial',
|
||||||
SettingsMachineContext,
|
Metric = 'metric',
|
||||||
Toggle,
|
}
|
||||||
UnitSystem,
|
|
||||||
initialSettings,
|
export const baseUnits = {
|
||||||
} from 'lib/settings'
|
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(
|
export const settingsMachine = createMachine(
|
||||||
{
|
{
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QGUwBc0EsB2VYDpMIAbMAYlTQAIAVACzAFswBtABgF1FQAHAe1iYsfbNxAAPRAA42+AEwB2KQFYAzGznKAnADZli1QBoQAT2kBGKfm37lOned3nzqgL6vjlLLgJFSFdCoAETAAMwBDAFdiagAFACc+ACswAGNqADlw5nYuJBB+QWFRfMkEABY5fDYa2rra83LjMwQdLWV8BXLyuxlVLU1Ld090bzxCEnJKYLComODMeLS0PniTXLFCoUwRMTK7fC1zNql7NgUjtnKjU0RlBSqpLVUVPVUda60tYZAvHHG-FNAgBVbBCKjIEywNBMDb5LbFPaILqdfRSORsS4qcxXZqIHqyK6qY4XOxsGTKco-P4+Cb+aYAIXCsDAVFBQjhvAE212pWkskUKnUml0+gUNxaqkU+EccnKF1UCnucnMcjcHl+o3+vkmZBofCgUFIMwARpEoFRYuFsGBiJyCtzEXzWrJlGxlKdVFKvfY1XiEBjyvhVOVzBdzu13pYFNStbTAQFqAB5bAmvjheIQf4QtDhNCRWD2hE7EqgfayHTEh7lHQNSxSf1Scz4cpHHFyFVujTKczuDXYPgQOBiGl4TaOktIhAAWg6X3nC4Xp39050sYw2rpYHHRUnztVhPJqmUlIGbEriv9WhrLZ6uibHcqUr7riAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QGUwBc0EsB2VYDpMIAbMAYlTQAIAVACzAFswBtABgF1FQAHAe1iYsfbNxAAPRAA42+AEwB2KQFYAzGznKAnADZli1QBoQAT2kBGKfm37lOned3nzqgL6vjlLLgJFSFdCoAETAAMwBDAFdiagAFACc+ACswAGNqADlw5nYuJBB+QWFRfMkEABY5fDYa2rra83LjMwQdLWV8BXLyuxlVLU1Ld090bzxCEnJKYLComODMeLS0PniTXLFCoUwRMTK7fC1zNql7NgUjtnKjU0RlBSqpLVUVPVUda60tYZAvHHG-FNAgBVbBCKjIEywNBMDb5LbFPaILqdfRSORsS4qcxXZqIHqyK6qY4XOxsGTKco-P4+Cb+aYAIXCsDAVFBQjhvAE212pWkskUKnUml0+gUNxaqkU+EccnKF1UCnucnMcjcHl+o3+vkmZBofCgUFIMwARpEoFRYuFsGBiJyCtzEXzWrJlGxlKdVFKvfY1XiEBjyvhVOVzBdzu13pYFNStbTAQFqAB5bAmvjheIQf4QtDhNCRWD2hE7EqgfayHTEh7lHQNSxSf1Scz4cpHHFyFVujTKczuDXYPgQOBiGl4TaOktIhAAWg6X3nC4Xp39050sYw2rpYHHRUnztVhPJqmUlIGbEriv9WhrLZ6uibHcqUr7riAA */
|
||||||
id: 'Settings',
|
id: 'Settings',
|
||||||
predictableActionArguments: true,
|
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',
|
initial: 'idle',
|
||||||
states: {
|
states: {
|
||||||
idle: {
|
idle: {
|
||||||
entry: ['setThemeClass'],
|
entry: ['setThemeClass'],
|
||||||
on: {
|
on: {
|
||||||
'Set All Settings': {
|
|
||||||
actions: [
|
|
||||||
assign((context, event) => {
|
|
||||||
return {
|
|
||||||
...context,
|
|
||||||
...event.data,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
'persistSettings',
|
|
||||||
'setThemeClass',
|
|
||||||
],
|
|
||||||
target: 'idle',
|
|
||||||
internal: true,
|
|
||||||
},
|
|
||||||
'Set Base Unit': {
|
'Set Base Unit': {
|
||||||
actions: [
|
actions: [
|
||||||
assign({
|
assign({
|
||||||
@ -151,7 +157,6 @@ export const settingsMachine = createMachine(
|
|||||||
tsTypes: {} as import('./settingsMachine.typegen').Typegen0,
|
tsTypes: {} as import('./settingsMachine.typegen').Typegen0,
|
||||||
schema: {
|
schema: {
|
||||||
events: {} as
|
events: {} as
|
||||||
| { type: 'Set All Settings'; data: Partial<SettingsMachineContext> }
|
|
||||||
| { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } }
|
| { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } }
|
||||||
| {
|
| {
|
||||||
type: 'Set Camera Controls'
|
type: 'Set Camera Controls'
|
||||||
@ -175,11 +180,6 @@ export const settingsMachine = createMachine(
|
|||||||
{
|
{
|
||||||
actions: {
|
actions: {
|
||||||
persistSettings: (context) => {
|
persistSettings: (context) => {
|
||||||
if (isTauri()) {
|
|
||||||
writeToSettingsFile(context).catch((err) => {
|
|
||||||
console.error('Error writing settings:', err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(SETTINGS_PERSIST_KEY, JSON.stringify(context))
|
localStorage.setItem(SETTINGS_PERSIST_KEY, JSON.stringify(context))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -29,9 +29,9 @@ import {
|
|||||||
getSortIcon,
|
getSortIcon,
|
||||||
} from '../lib/sorting'
|
} from '../lib/sorting'
|
||||||
import useStateMachineCommands from '../hooks/useStateMachineCommands'
|
import useStateMachineCommands from '../hooks/useStateMachineCommands'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
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 { sep } from '@tauri-apps/api/path'
|
||||||
import { homeCommandBarConfig } from 'lib/commandBarConfigs/homeCommandConfig'
|
import { homeCommandBarConfig } from 'lib/commandBarConfigs/homeCommandConfig'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
@ -42,17 +42,14 @@ import { isTauri } from 'lib/isTauri'
|
|||||||
const Home = () => {
|
const Home = () => {
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const {
|
const { projects: loadedProjects, newDefaultDirectory } =
|
||||||
projects: loadedProjects,
|
useLoaderData() as HomeLoaderData
|
||||||
newDefaultDirectory,
|
|
||||||
error,
|
|
||||||
} = useLoaderData() as HomeLoaderData
|
|
||||||
const {
|
const {
|
||||||
settings: {
|
settings: {
|
||||||
context: { defaultDirectory, defaultProjectName },
|
context: { defaultDirectory, defaultProjectName },
|
||||||
send: sendToSettings,
|
send: sendToSettings,
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
// Set the default directory if it's been updated
|
// Set the default directory if it's been updated
|
||||||
// during the loading of the home page. This is wrapped
|
// during the loading of the home page. This is wrapped
|
||||||
@ -60,17 +57,11 @@ const Home = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (newDefaultDirectory) {
|
if (newDefaultDirectory) {
|
||||||
sendToSettings({
|
sendToSettings({
|
||||||
type: 'Set All Settings',
|
type: 'Set Default Directory',
|
||||||
data: { defaultDirectory: newDefaultDirectory },
|
data: { defaultDirectory: newDefaultDirectory },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toast any errors that occurred during the loading process
|
|
||||||
if (error) {
|
|
||||||
toast.error(error.message)
|
|
||||||
}
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
isTauri() ? 'mod+,' : 'shift+mod+,',
|
isTauri() ? 'mod+,' : 'shift+mod+,',
|
||||||
() => navigate(paths.HOME + paths.SETTINGS),
|
() => navigate(paths.HOME + paths.SETTINGS),
|
||||||
|
@ -2,7 +2,7 @@ import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
|||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useStore } from '../../useStore'
|
import { useStore } from '../../useStore'
|
||||||
import { SettingsSection } from 'routes/Settings'
|
import { SettingsSection } from 'routes/Settings'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import {
|
import {
|
||||||
CameraSystem,
|
CameraSystem,
|
||||||
cameraMouseDragGuards,
|
cameraMouseDragGuards,
|
||||||
@ -22,7 +22,7 @@ export default function Units() {
|
|||||||
context: { cameraControls },
|
context: { cameraControls },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
|
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
useNextClick,
|
useNextClick,
|
||||||
} from '.'
|
} from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
import {
|
import {
|
||||||
@ -31,7 +31,7 @@ function OnboardingWithNewFile() {
|
|||||||
settings: {
|
settings: {
|
||||||
context: { defaultDirectory },
|
context: { defaultDirectory },
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
async function createAndOpenNewProject() {
|
async function createAndOpenNewProject() {
|
||||||
const projects = await getProjectsInDir(defaultDirectory)
|
const projects = await getProjectsInDir(defaultDirectory)
|
||||||
@ -111,7 +111,7 @@ export default function Introduction() {
|
|||||||
context: { theme },
|
context: { theme },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
const getLogoTheme = () =>
|
const getLogoTheme = () =>
|
||||||
theme === Themes.Light ||
|
theme === Themes.Light ||
|
||||||
(theme === Themes.System && getSystemTheme() === Themes.Light)
|
(theme === Themes.System && getSystemTheme() === Themes.Light)
|
||||||
|
@ -3,7 +3,7 @@ import { onboardingPaths } from 'routes/Onboarding/paths'
|
|||||||
import { useStore } from '../../useStore'
|
import { useStore } from '../../useStore'
|
||||||
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
|
||||||
export default function ParametricModeling() {
|
export default function ParametricModeling() {
|
||||||
const { buttonDownInStream } = useStore((s) => ({
|
const { buttonDownInStream } = useStore((s) => ({
|
||||||
@ -13,7 +13,7 @@ export default function ParametricModeling() {
|
|||||||
settings: {
|
settings: {
|
||||||
context: { theme },
|
context: { theme },
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
const getImageTheme = () =>
|
const getImageTheme = () =>
|
||||||
theme === Themes.Light ||
|
theme === Themes.Light ||
|
||||||
(theme === Themes.System && getSystemTheme() === Themes.Light)
|
(theme === Themes.System && getSystemTheme() === Themes.Light)
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
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 { ActionButton } from '../../components/ActionButton'
|
||||||
import { SettingsSection } from '../Settings'
|
import { SettingsSection } from '../Settings'
|
||||||
import { Toggle } from '../../components/Toggle/Toggle'
|
import { Toggle } from '../../components/Toggle/Toggle'
|
||||||
import { useDismiss, useNextClick } from '.'
|
import { useDismiss, useNextClick } from '.'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
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() {
|
export default function Units() {
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
@ -15,7 +16,7 @@ export default function Units() {
|
|||||||
send,
|
send,
|
||||||
context: { unitSystem, baseUnit },
|
context: { unitSystem, baseUnit },
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
||||||
|
@ -5,7 +5,7 @@ import Camera from './Camera'
|
|||||||
import Sketching from './Sketching'
|
import Sketching from './Sketching'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
|
import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import Streaming from './Streaming'
|
import Streaming from './Streaming'
|
||||||
import CodeEditor from './CodeEditor'
|
import CodeEditor from './CodeEditor'
|
||||||
import ParametricModeling from './ParametricModeling'
|
import ParametricModeling from './ParametricModeling'
|
||||||
@ -78,7 +78,7 @@ export function useNextClick(newStatus: string) {
|
|||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
const {
|
const {
|
||||||
settings: { send },
|
settings: { send },
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
return useCallback(() => {
|
return useCallback(() => {
|
||||||
@ -94,7 +94,7 @@ export function useDismiss() {
|
|||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
const {
|
const {
|
||||||
settings: { send },
|
settings: { send },
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
return useCallback(() => {
|
return useCallback(() => {
|
||||||
|
@ -5,38 +5,31 @@ import { open } from '@tauri-apps/api/dialog'
|
|||||||
import {
|
import {
|
||||||
BaseUnit,
|
BaseUnit,
|
||||||
DEFAULT_PROJECT_NAME,
|
DEFAULT_PROJECT_NAME,
|
||||||
SETTINGS_PERSIST_KEY,
|
|
||||||
baseUnits,
|
baseUnits,
|
||||||
initialSettings,
|
} from '../machines/settingsMachine'
|
||||||
UnitSystem,
|
|
||||||
} from 'lib/settings'
|
|
||||||
import { Toggle } from '../components/Toggle/Toggle'
|
import { Toggle } from '../components/Toggle/Toggle'
|
||||||
import { useLocation, useNavigate, useRouteLoaderData } from 'react-router-dom'
|
import { useLocation, useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { Themes } from '../lib/theme'
|
import { Themes } from '../lib/theme'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import {
|
import {
|
||||||
CameraSystem,
|
CameraSystem,
|
||||||
cameraSystems,
|
cameraSystems,
|
||||||
cameraMouseDragGuards,
|
cameraMouseDragGuards,
|
||||||
} from 'lib/cameraControls'
|
} from 'lib/cameraControls'
|
||||||
|
import { UnitSystem } from 'machines/settingsMachine'
|
||||||
import { useDotDotSlash } from 'hooks/useDotDotSlash'
|
import { useDotDotSlash } from 'hooks/useDotDotSlash'
|
||||||
import {
|
import {
|
||||||
createNewProject,
|
createNewProject,
|
||||||
getNextProjectIndex,
|
getNextProjectIndex,
|
||||||
getProjectsInDir,
|
getProjectsInDir,
|
||||||
getSettingsFilePath,
|
|
||||||
initializeProjectDirectory,
|
|
||||||
interpolateProjectNameWithIndex,
|
interpolateProjectNameWithIndex,
|
||||||
} from 'lib/tauriFS'
|
} from 'lib/tauriFS'
|
||||||
import { ONBOARDING_PROJECT_NAME } from './Onboarding'
|
import { ONBOARDING_PROJECT_NAME } from './Onboarding'
|
||||||
import { sep } from '@tauri-apps/api/path'
|
import { sep } from '@tauri-apps/api/path'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
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 = () => {
|
export const Settings = () => {
|
||||||
const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
|
const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
|
||||||
@ -62,14 +55,11 @@ export const Settings = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
async function handleDirectorySelection() {
|
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({
|
const newDirectory = await open({
|
||||||
directory: true,
|
directory: true,
|
||||||
recursive: true,
|
|
||||||
defaultPath: defaultDirectory || paths.INDEX,
|
defaultPath: defaultDirectory || paths.INDEX,
|
||||||
title: 'Choose a new default directory',
|
title: 'Choose a new default directory',
|
||||||
})
|
})
|
||||||
@ -316,59 +306,6 @@ export const Settings = () => {
|
|||||||
Replay Onboarding
|
Replay Onboarding
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</SettingsSection>
|
</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">
|
<p className="mt-24 text-sm font-mono">
|
||||||
{/* This uses a Vite plugin, set in vite.config.ts
|
{/* This uses a Vite plugin, set in vite.config.ts
|
||||||
to inject the version from package.json */}
|
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 { VITE_KC_SITE_BASE_URL, VITE_KC_API_BASE_URL } from '../env'
|
||||||
import { Themes, getSystemTheme } from '../lib/theme'
|
import { Themes, getSystemTheme } from '../lib/theme'
|
||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
|
|
||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
@ -20,7 +20,7 @@ const SignIn = () => {
|
|||||||
context: { theme },
|
context: { theme },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} = useSettingsAuthContext()
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
const signInTauri = async () => {
|
const signInTauri = async () => {
|
||||||
// We want to invoke our command to login via device auth.
|
// We want to invoke our command to login via device auth.
|
||||||
|
Reference in New Issue
Block a user