Compare commits

...

4 Commits

Author SHA1 Message Date
0f8dfe528f updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-05 19:29:54 -08:00
006663d83c fixes
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-05 19:26:31 -08:00
8e248f43ab updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-05 19:24:41 -08:00
09ef1d5f10 updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-05 18:57:10 -08:00
47 changed files with 538 additions and 406 deletions

View File

@ -12,9 +12,10 @@ import { CmdBarFixture } from './cmdBarFixture'
import { EditorFixture } from './editorFixture' import { EditorFixture } from './editorFixture'
import { ToolbarFixture } from './toolbarFixture' import { ToolbarFixture } from './toolbarFixture'
import { SceneFixture } from './sceneFixture' import { SceneFixture } from './sceneFixture'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { HomePageFixture } from './homePageFixture' import { HomePageFixture } from './homePageFixture'
import { unsafeTypedKeys } from 'lib/utils' import { unsafeTypedKeys } from 'lib/utils'
import { DeepPartial } from 'lib/types'
import { Settings } from 'wasm-lib/kcl/bindings/Settings'
export class AuthenticatedApp { export class AuthenticatedApp {
public readonly page: Page public readonly page: Page
@ -78,7 +79,7 @@ export class AuthenticatedTronApp {
fixtures: Partial<Fixtures> fixtures: Partial<Fixtures>
folderSetupFn?: (projectDirName: string) => Promise<void> folderSetupFn?: (projectDirName: string) => Promise<void>
cleanProjectDir?: boolean cleanProjectDir?: boolean
appSettings?: Partial<SaveSettingsPayload> appSettings?: DeepPartial<Settings>
} = { fixtures: {} } } = { fixtures: {} }
) { ) {
const { electronApp, page, context, dir } = await setupElectron({ const { electronApp, page, context, dir } = await setupElectron({

View File

@ -1,7 +1,12 @@
import { test, expect } from './zoo-test' import { test, expect } from './zoo-test'
import { join } from 'path' import { join } from 'path'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import { getUtils, executorInputPath, createProject } from './test-utils' import {
getUtils,
executorInputPath,
createProject,
settingsToToml,
} from './test-utils'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { import {
@ -10,7 +15,6 @@ import {
TEST_SETTINGS_ONBOARDING_EXPORT, TEST_SETTINGS_ONBOARDING_EXPORT,
TEST_SETTINGS_ONBOARDING_USER_MENU, TEST_SETTINGS_ONBOARDING_USER_MENU,
} from './storageStates' } from './storageStates'
import * as TOML from '@iarna/toml'
import { expectPixelColor } from './fixtures/sceneFixture' import { expectPixelColor } from './fixtures/sceneFixture'
// Because onboarding relies on an app setting we need to set it as incompletel // Because onboarding relies on an app setting we need to set it as incompletel
@ -22,7 +26,7 @@ test.describe('Onboarding tests', () => {
{ {
appSettings: { appSettings: {
app: { app: {
onboardingStatus: 'incomplete', onboarding_status: 'incomplete',
}, },
}, },
cleanProjectDir: true, cleanProjectDir: true,
@ -63,7 +67,7 @@ test.describe('Onboarding tests', () => {
tag: '@electron', tag: '@electron',
appSettings: { appSettings: {
app: { app: {
onboardingStatus: 'incomplete', onboarding_status: 'incomplete',
}, },
}, },
cleanProjectDir: true, cleanProjectDir: true,
@ -108,7 +112,7 @@ test.describe('Onboarding tests', () => {
{ {
appSettings: { appSettings: {
app: { app: {
onboardingStatus: 'incomplete', onboarding_status: 'incomplete',
}, },
}, },
cleanProjectDir: true, cleanProjectDir: true,
@ -158,7 +162,7 @@ test.describe('Onboarding tests', () => {
{ {
appSettings: { appSettings: {
app: { app: {
onboardingStatus: 'incomplete', onboarding_status: 'incomplete',
}, },
}, },
}, },
@ -172,7 +176,7 @@ test.describe('Onboarding tests', () => {
}, },
{ {
settingsKey: TEST_SETTINGS_KEY, settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_START, settings: TEST_SETTINGS_ONBOARDING_START,
}), }),
} }
@ -208,7 +212,7 @@ test.describe('Onboarding tests', () => {
{ {
appSettings: { appSettings: {
app: { app: {
onboardingStatus: '/export', onboarding_status: '/export',
}, },
}, },
cleanProjectDir: true, cleanProjectDir: true,
@ -225,7 +229,7 @@ test.describe('Onboarding tests', () => {
}, },
{ {
settingsKey: TEST_SETTINGS_KEY, settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_EXPORT, settings: TEST_SETTINGS_ONBOARDING_EXPORT,
}), }),
} }
@ -263,7 +267,7 @@ test.describe('Onboarding tests', () => {
{ {
appSettings: { appSettings: {
app: { app: {
onboardingStatus: '/parametric-modeling', onboarding_status: '/parametric-modeling',
}, },
}, },
cleanProjectDir: true, cleanProjectDir: true,
@ -313,7 +317,7 @@ test.describe('Onboarding tests', () => {
{ {
appSettings: { appSettings: {
app: { app: {
onboardingStatus: 'incomplete', onboarding_status: 'incomplete',
}, },
}, },
cleanProjectDir: true, cleanProjectDir: true,
@ -326,7 +330,7 @@ test.describe('Onboarding tests', () => {
}, },
{ {
settingsKey: TEST_SETTINGS_KEY, settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_USER_MENU, settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
}), }),
} }
@ -386,7 +390,7 @@ test.describe('Onboarding tests', () => {
{ {
appSettings: { appSettings: {
app: { app: {
onboardingStatus: 'incomplete', onboarding_status: 'incomplete',
}, },
}, },
cleanProjectDir: true, cleanProjectDir: true,
@ -400,7 +404,7 @@ test.describe('Onboarding tests', () => {
}, },
{ {
settingsKey: TEST_SETTINGS_KEY, settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_USER_MENU, settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
}), }),
} }
@ -442,7 +446,7 @@ test.fixme(
{ {
appSettings: { appSettings: {
app: { app: {
onboardingStatus: 'dismissed', onboarding_status: 'dismissed',
}, },
}, },
cleanProjectDir: true, cleanProjectDir: true,

View File

@ -1,6 +1,6 @@
import { test, expect } from '@playwright/test' import { test, expect } from '@playwright/test'
import { secrets } from './secrets' import { secrets } from './secrets'
import { Paths, doExport, getUtils } from './test-utils' import { Paths, doExport, getUtils, settingsToToml } from './test-utils'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import { spawn } from 'child_process' import { spawn } from 'child_process'
@ -12,7 +12,6 @@ import {
TEST_SETTINGS, TEST_SETTINGS,
TEST_SETTINGS_KEY, TEST_SETTINGS_KEY,
} from './storageStates' } from './storageStates'
import * as TOML from '@iarna/toml'
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
// reducedMotion kills animations, which speeds up tests and reduces flakiness // reducedMotion kills animations, which speeds up tests and reduces flakiness
@ -29,7 +28,7 @@ test.beforeEach(async ({ page }) => {
{ {
token: secrets.token, token: secrets.token,
settingsKey: TEST_SETTINGS_KEY, settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS }), settings: settingsToToml({ settings: TEST_SETTINGS }),
IS_PLAYWRIGHT_KEY: IS_PLAYWRIGHT_KEY, IS_PLAYWRIGHT_KEY: IS_PLAYWRIGHT_KEY,
} }
) )
@ -704,12 +703,12 @@ test.describe(
}, },
{ {
settingsKey: TEST_SETTINGS_KEY, settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: settingsToToml({
settings: { settings: {
...TEST_SETTINGS, ...TEST_SETTINGS,
modeling: { modeling: {
...TEST_SETTINGS.modeling, ...TEST_SETTINGS.modeling,
defaultUnit: 'mm', base_unit: 'mm',
}, },
}, },
}), }),
@ -1062,12 +1061,12 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
}, },
{ {
settingsKey: TEST_SETTINGS_KEY, settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: settingsToToml({
settings: { settings: {
...TEST_SETTINGS, ...TEST_SETTINGS,
modeling: { modeling: {
...TEST_SETTINGS.modeling, ...TEST_SETTINGS.modeling,
showScaleGrid: true, show_scale_grid: true,
}, },
}, },
}), }),

View File

@ -1,78 +1,83 @@
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
import { DeepPartial } from 'lib/types'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { Settings } from 'wasm-lib/kcl/bindings/Settings'
export const IS_PLAYWRIGHT_KEY = 'playwright' export const IS_PLAYWRIGHT_KEY = 'playwright'
export const TEST_SETTINGS_KEY = '/settings.toml' export const TEST_SETTINGS_KEY = '/settings.toml'
export const TEST_SETTINGS = { export const TEST_SETTINGS = {
app: { app: {
onboarding_status: 'dismissed',
appearance: {
theme: Themes.Dark, theme: Themes.Dark,
onboardingStatus: 'dismissed', },
projectDirectory: '', show_debug_panel: true,
enableSSAO: false,
}, },
modeling: { modeling: {
defaultUnit: 'in', base_unit: 'in',
mouseControls: 'Zoo', mouse_controls: 'zoo',
cameraProjection: 'perspective', camera_projection: 'perspective',
showDebugPanel: true, enable_ssao: false,
}, },
projects: { project: {
defaultProjectName: 'project-$nnn', default_project_name: 'project-$nnn',
directory: '',
}, },
textEditor: { text_editor: {
textWrapping: true, text_wrapping: true,
}, },
} satisfies Partial<SaveSettingsPayload> } satisfies DeepPartial<Settings>
export const TEST_SETTINGS_ONBOARDING_USER_MENU = { export const TEST_SETTINGS_ONBOARDING_USER_MENU = {
...TEST_SETTINGS, ...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, onboardingStatus: onboardingPaths.USER_MENU }, app: { ...TEST_SETTINGS.app, onboarding_status: onboardingPaths.USER_MENU },
} satisfies Partial<SaveSettingsPayload> } satisfies DeepPartial<Settings>
export const TEST_SETTINGS_ONBOARDING_EXPORT = { export const TEST_SETTINGS_ONBOARDING_EXPORT = {
...TEST_SETTINGS, ...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, onboardingStatus: onboardingPaths.EXPORT }, app: { ...TEST_SETTINGS.app, onboarding_status: onboardingPaths.EXPORT },
} satisfies Partial<SaveSettingsPayload> } satisfies DeepPartial<Settings>
export const TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING = { export const TEST_SETTINGS_ONBOARDING_PARAMETRIC_MODELING = {
...TEST_SETTINGS, ...TEST_SETTINGS,
app: { app: {
...TEST_SETTINGS.app, ...TEST_SETTINGS.app,
onboardingStatus: onboardingPaths.PARAMETRIC_MODELING, onboarding_status: onboardingPaths.PARAMETRIC_MODELING,
}, },
} satisfies Partial<SaveSettingsPayload> } satisfies DeepPartial<Settings>
export const TEST_SETTINGS_ONBOARDING_START = { export const TEST_SETTINGS_ONBOARDING_START = {
...TEST_SETTINGS, ...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, onboardingStatus: '' }, app: { ...TEST_SETTINGS.app, onboarding_status: '' },
} satisfies Partial<SaveSettingsPayload> } satisfies DeepPartial<Settings>
export const TEST_SETTINGS_DEFAULT_THEME = { export const TEST_SETTINGS_DEFAULT_THEME = {
...TEST_SETTINGS, ...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, theme: Themes.System }, app: { ...TEST_SETTINGS.app, appearance: { theme: Themes.System } },
} satisfies Partial<SaveSettingsPayload> } satisfies DeepPartial<Settings>
export const TEST_SETTINGS_CORRUPTED = { export const TEST_SETTINGS_CORRUPTED = {
app: { app: {
onboarding_status: 'dismissed',
appearance: {
theme: Themes.Dark, theme: Themes.Dark,
onboardingStatus: 'dismissed', },
projectDirectory: 123 as any, show_debug_panel: true,
}, },
modeling: { modeling: {
defaultUnit: 'invalid' as any, base_unit: 'invalid' as any,
mouseControls: `() => alert('hack the planet')` as any, mouse_controls: `() => alert('hack the planet')` as any,
cameraProjection: 'perspective', camera_projection: 'perspective',
showDebugPanel: true,
}, },
projects: { project: {
defaultProjectName: false as any, default_project_name: false as any,
directory: 123 as any,
}, },
textEditor: { text_editor: {
textWrapping: true, text_wrapping: true,
}, },
} satisfies Partial<SaveSettingsPayload> } satisfies DeepPartial<Settings>
export const TEST_CODE_GIZMO = `part001 = startSketchOn('XZ') export const TEST_CODE_GIZMO = `part001 = startSketchOn('XZ')
|> startProfileAt([20, 0], %) |> startProfileAt([20, 0], %)

View File

@ -23,11 +23,13 @@ import {
IS_PLAYWRIGHT_KEY, IS_PLAYWRIGHT_KEY,
} from './storageStates' } from './storageStates'
import * as TOML from '@iarna/toml' import * as TOML from '@iarna/toml'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { SETTINGS_FILE_NAME } from 'lib/constants' import { SETTINGS_FILE_NAME } from 'lib/constants'
import { isErrorWhitelisted } from './lib/console-error-whitelist' import { isErrorWhitelisted } from './lib/console-error-whitelist'
import { isArray } from 'lib/utils' import { isArray } from 'lib/utils'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { Configuration } from 'lang/wasm'
import { DeepPartial } from 'lib/types'
import { Settings } from 'wasm-lib/kcl/bindings/Settings'
const toNormalizedCode = (text: string) => { const toNormalizedCode = (text: string) => {
return text.replace(/\s+/g, '') return text.replace(/\s+/g, '')
@ -884,19 +886,23 @@ export async function setup(
{ {
token: secrets.token, token: secrets.token,
settingsKey: TEST_SETTINGS_KEY, settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: settingsToToml({
settings: { settings: {
...TEST_SETTINGS, ...TEST_SETTINGS,
app: { app: {
...TEST_SETTINGS.projects, ...TEST_SETTINGS.app,
projectDirectory: TEST_SETTINGS.app.projectDirectory, onboarding_status: 'dismissed',
onboardingStatus: 'dismissed', appearance: {
theme: 'dark', theme: 'dark',
}, },
} as Partial<SaveSettingsPayload>, },
project: {
directory: TEST_SETTINGS.project.directory,
},
},
}), }),
IS_PLAYWRIGHT_KEY, IS_PLAYWRIGHT_KEY,
PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.app.projectDirectory, PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.project.directory,
PERSIST_MODELING_CONTEXT, PERSIST_MODELING_CONTEXT,
} }
) )
@ -931,7 +937,7 @@ export async function setupElectron({
testInfo: TestInfo testInfo: TestInfo
folderSetupFn?: (projectDirName: string) => Promise<void> folderSetupFn?: (projectDirName: string) => Promise<void>
cleanProjectDir?: boolean cleanProjectDir?: boolean
appSettings?: Partial<SaveSettingsPayload> appSettings?: DeepPartial<Settings>
}): Promise<{ }): Promise<{
electronApp: ElectronApplication electronApp: ElectronApplication
context: BrowserContext context: BrowserContext
@ -978,7 +984,7 @@ export async function setupElectron({
if (cleanProjectDir) { if (cleanProjectDir) {
const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME) const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME)
const settingsOverrides = TOML.stringify( const settingsOverrides = settingsToToml(
appSettings appSettings
? { ? {
settings: { settings: {
@ -986,9 +992,11 @@ export async function setupElectron({
...appSettings, ...appSettings,
app: { app: {
...TEST_SETTINGS.app, ...TEST_SETTINGS.app,
projectDirectory: projectDirName,
...appSettings.app, ...appSettings.app,
}, },
project: {
directory: projectDirName,
},
}, },
} }
: { : {
@ -996,7 +1004,9 @@ export async function setupElectron({
...TEST_SETTINGS, ...TEST_SETTINGS,
app: { app: {
...TEST_SETTINGS.app, ...TEST_SETTINGS.app,
projectDirectory: projectDirName, },
project: {
directory: projectDirName,
}, },
}, },
} }
@ -1190,3 +1200,7 @@ export async function pollEditorLinesSelectedLength(page: Page, lines: number) {
}) })
.toBe(lines) .toBe(lines)
} }
export function settingsToToml(settings: DeepPartial<Configuration>) {
return TOML.stringify(settings)
}

View File

@ -31,13 +31,13 @@ test.describe('Testing settings', () => {
) )
) as { settings: SaveSettingsPayload } ) as { settings: SaveSettingsPayload }
expect(storedSettings.settings?.app?.theme).toBe('dark') expect(storedSettings.settings?.app?.appearance?.theme).toBe('dark')
// Check that the invalid settings were changed to good defaults // Check that the invalid settings were changed to good defaults
expect(storedSettings.settings?.modeling?.defaultUnit).toBe('in') expect(storedSettings.settings?.modeling?.base_unit).toBe('in')
expect(storedSettings.settings?.modeling?.mouseControls).toBe('Zoo') expect(storedSettings.settings?.modeling?.mouse_controls).toBe('Zoo')
expect(storedSettings.settings?.app?.projectDirectory).toBe('') expect(storedSettings.settings?.project?.directory).toBe('')
expect(storedSettings.settings?.projects?.defaultProjectName).toBe( expect(storedSettings.settings?.project?.default_project_name).toBe(
'project-$nnn' 'project-$nnn'
) )
} }
@ -374,7 +374,9 @@ test.describe('Testing settings', () => {
tag: '@electron', tag: '@electron',
appSettings: { appSettings: {
app: { app: {
themeColor: '259', appearance: {
color: 259,
},
}, },
}, },
}, },
@ -400,9 +402,11 @@ test.describe('Testing settings', () => {
tag: '@electron', tag: '@electron',
appSettings: { appSettings: {
app: { app: {
appearance: {
// Doesn't matter what you set it to. It will // Doesn't matter what you set it to. It will
// default to 264.5 // default to 264.5
themeColor: '0', color: 0,
},
}, },
}, },
}, },
@ -832,7 +836,7 @@ test.describe('Testing settings', () => {
// but "show debug panel" set to false // but "show debug panel" set to false
appSettings: { appSettings: {
...TEST_SETTINGS, ...TEST_SETTINGS,
modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false }, app: { ...TEST_SETTINGS.app, show_debug_panel: false },
}, },
}, },
async ({ context, page, homePage }) => { async ({ context, page, homePage }) => {

View File

@ -18,7 +18,8 @@ import {
AuthenticatedApp, AuthenticatedApp,
} from './fixtures/fixtureSetup' } from './fixtures/fixtureSetup'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes' import { Settings } from 'wasm-lib/kcl/bindings/Settings'
import { DeepPartial } from 'lib/types'
export { expect } from '@playwright/test' export { expect } from '@playwright/test'
declare module '@playwright/test' { declare module '@playwright/test' {
@ -45,7 +46,7 @@ export type BrowserContext = BrowserContextPlaywright
export type Page = PagePlaywright export type Page = PagePlaywright
export type TestDetails = TestDetailsPlaywright & { export type TestDetails = TestDetailsPlaywright & {
cleanProjectDir?: boolean cleanProjectDir?: boolean
appSettings?: Partial<SaveSettingsPayload> appSettings?: DeepPartial<Settings>
} }
// Our custom decorated Zoo test object. Makes it easier to add fixtures, and // Our custom decorated Zoo test object. Makes it easier to add fixtures, and

View File

@ -54,7 +54,7 @@ export function App() {
) )
const { const {
app: { onboardingStatus }, app: { onboarding_status },
} = settings.context } = settings.context
useHotkeys('backspace', (e) => { useHotkeys('backspace', (e) => {
@ -69,7 +69,7 @@ export function App() {
) )
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some( const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
(p) => p === onboardingStatus.current (p) => p === onboarding_status.current
) )
? 'opacity-20' ? 'opacity-20'
: '' : ''

View File

@ -77,7 +77,7 @@ export const ClientSideScene = ({
}: { }: {
cameraControls: ReturnType< cameraControls: ReturnType<
typeof useSettingsAuthContext typeof useSettingsAuthContext
>['settings']['context']['modeling']['mouseControls']['current'] >['settings']['context']['modeling']['mouse_controls']['current']
}) => { }) => {
const canvasRef = useRef<HTMLDivElement>(null) const canvasRef = useRef<HTMLDivElement>(null)
const { state, send, context } = useModelingContext() const { state, send, context } = useModelingContext()

View File

@ -5,21 +5,21 @@ import { useEffect, useState } from 'react'
export function CameraProjectionToggle() { export function CameraProjectionToggle() {
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const isCameraProjectionPerspective = const isCameraProjectionPerspective =
settings.context.modeling.cameraProjection.current === 'perspective' settings.context.modeling.camera_projection.current === 'perspective'
const [checked, setChecked] = useState(isCameraProjectionPerspective) const [checked, setChecked] = useState(isCameraProjectionPerspective)
useEffect(() => { useEffect(() => {
setChecked( setChecked(
settings.context.modeling.cameraProjection.current === 'perspective' settings.context.modeling.camera_projection.current === 'perspective'
) )
}, [settings.context.modeling.cameraProjection.current]) }, [settings.context.modeling.camera_projection.current])
return ( return (
<Switch <Switch
checked={checked} checked={checked}
onChange={(newValue) => { onChange={(newValue) => {
settings.send({ settings.send({
type: 'set.modeling.cameraProjection', type: 'set.modeling.camera_projection',
data: { data: {
level: 'user', level: 'user',
value: newValue ? 'perspective' : 'orthographic', value: newValue ? 'perspective' : 'orthographic',

View File

@ -115,9 +115,9 @@ function CommandBarKclInput({
: defaultValue.length, : defaultValue.length,
}, },
theme: theme:
settings.context.app.theme.current === 'system' settings.context.app.appearance.theme.current === 'system'
? getSystemTheme() ? getSystemTheme()
: settings.context.app.theme.current, : settings.context.app.appearance.theme.current,
extensions: [ extensions: [
varMentionsExtension, varMentionsExtension,
EditorView.updateListener.of((vu: ViewUpdate) => { EditorView.updateListener.of((vu: ViewUpdate) => {

View File

@ -6,7 +6,7 @@ import { useState } from 'react'
const DownloadAppBanner = () => { const DownloadAppBanner = () => {
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const [isBannerDismissed, setIsBannerDismissed] = useState( const [isBannerDismissed, setIsBannerDismissed] = useState(
settings.context.app.dismissWebBanner.current settings.context.app.dismiss_web_banner.current
) )
return ( return (

View File

@ -315,7 +315,7 @@ export const FileMachineProvider = ({
// with the sample's setting. // with the sample's setting.
if (data.sampleUnits) { if (data.sampleUnits) {
settings.send({ settings.send({
type: 'set.modeling.defaultUnit', type: 'set.modeling.base_unit',
data: { data: {
level: 'project', level: 'project',
value: data.sampleUnits, value: data.sampleUnits,

View File

@ -107,7 +107,7 @@ export function HelpMenu(props: React.PropsWithChildren) {
as="button" as="button"
onClick={() => { onClick={() => {
settings.send({ settings.send({
type: 'set.app.onboardingStatus', type: 'set.app.onboarding_status',
data: { data: {
value: '', value: '',
level: 'user', level: 'user',

View File

@ -111,12 +111,15 @@ export const ModelingMachineProvider = ({
auth, auth,
settings: { settings: {
context: { context: {
app: { theme, enableSSAO }, app: {
appearance: { theme },
},
modeling: { modeling: {
defaultUnit, base_unit,
cameraProjection, camera_projection,
highlightEdges, highlight_edges,
showScaleGrid, show_scale_grid,
enable_ssao,
}, },
}, },
}, },
@ -172,7 +175,7 @@ export const ModelingMachineProvider = ({
sceneInfra.camControls.syncDirection = 'clientToEngine' sceneInfra.camControls.syncDirection = 'clientToEngine'
if (cameraProjection.current === 'perspective') { if (camera_projection.current === 'perspective') {
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine() await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
} }
@ -508,7 +511,7 @@ export const ModelingMachineProvider = ({
format.type === 'stl' || format.type === 'stl' ||
format.type === 'ply' format.type === 'ply'
) { ) {
format.units = defaultUnit.current format.units = base_unit.current
} }
if (format.type === 'ply' || format.type === 'stl') { if (format.type === 'ply' || format.type === 'stl') {
@ -533,7 +536,7 @@ export const ModelingMachineProvider = ({
token, token,
settings: { settings: {
theme: theme.current, theme: theme.current,
highlightEdges: highlightEdges.current, highlightEdges: highlight_edges.current,
}, },
}).catch(reportRejection) }).catch(reportRejection)
}, },
@ -1134,10 +1137,10 @@ export const ModelingMachineProvider = ({
{ {
pool: pool, pool: pool,
theme: theme.current, theme: theme.current,
highlightEdges: highlightEdges.current, highlightEdges: highlight_edges.current,
enableSSAO: enableSSAO.current, enableSSAO: enable_ssao.current,
showScaleGrid: showScaleGrid.current, showScaleGrid: show_scale_grid.current,
cameraProjection: cameraProjection.current, cameraProjection: camera_projection.current,
}, },
token token
) )

View File

@ -69,7 +69,7 @@ export const ModelingPane = ({
...props ...props
}: ModelingPaneProps) => { }: ModelingPaneProps) => {
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const onboardingStatus = settings.context.app.onboardingStatus const onboardingStatus = settings.context.app.onboarding_status
const pointerEventsCssClass = const pointerEventsCssClass =
onboardingStatus.current === onboardingPaths.CAMERA onboardingStatus.current === onboardingPaths.CAMERA
? 'pointer-events-none ' ? 'pointer-events-none '

View File

@ -69,9 +69,9 @@ export const KclEditorPane = () => {
const lastSelectionEvent = useSelector(kclEditorActor, selectionEventSelector) const lastSelectionEvent = useSelector(kclEditorActor, selectionEventSelector)
const editorIsMounted = useSelector(kclEditorActor, editorIsMountedSelector) const editorIsMounted = useSelector(kclEditorActor, editorIsMountedSelector)
const theme = const theme =
context.app.theme.current === Themes.System context.app.appearance.theme.current === Themes.System
? getSystemTheme() ? getSystemTheme()
: context.app.theme.current : context.app.appearance.theme.current
const { copilotLSP, kclLSP } = useLspContext() const { copilotLSP, kclLSP } = useLspContext()
// Since these already exist in the editor, we don't need to define them // Since these already exist in the editor, we don't need to define them
@ -104,8 +104,8 @@ export const KclEditorPane = () => {
}) })
}, [editorIsMounted, lastSelectionEvent]) }, [editorIsMounted, lastSelectionEvent])
const textWrapping = context.textEditor.textWrapping const textWrapping = context.text_editor.text_wrapping
const cursorBlinking = context.textEditor.blinkingCursor const cursorBlinking = context.text_editor.blinking_cursor
// DO NOT ADD THE CODEMIRROR HOTKEYS HERE TO THE DEPENDENCY ARRAY // DO NOT ADD THE CODEMIRROR HOTKEYS HERE TO THE DEPENDENCY ARRAY
// It reloads the editor every time we do _anything_ in the editor // It reloads the editor every time we do _anything_ in the editor
// I have no idea why. // I have no idea why.

View File

@ -210,6 +210,6 @@ export const sidebarPanes: SidebarPane[] = [
) )
}, },
keybinding: 'Shift + D', keybinding: 'Shift + D',
hide: ({ settings }) => !settings.modeling.showDebugPanel.current, hide: ({ settings }) => !settings.app.show_debug_panel.current,
}, },
] ]

View File

@ -40,14 +40,14 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
const { commandBarSend } = useCommandsContext() const { commandBarSend } = useCommandsContext()
const kclContext = useKclContext() const kclContext = useKclContext()
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const onboardingStatus = settings.context.app.onboardingStatus const onboardingStatus = settings.context.app.onboarding_status
const { send, context } = useModelingContext() const { send, context } = useModelingContext()
const pointerEventsCssClass = const pointerEventsCssClass =
onboardingStatus.current === onboardingPaths.CAMERA || onboardingStatus.current === onboardingPaths.CAMERA ||
context.store?.openPanes.length === 0 context.store?.openPanes.length === 0
? 'pointer-events-none ' ? 'pointer-events-none '
: 'pointer-events-auto ' : 'pointer-events-auto '
const showDebugPanel = settings.context.modeling.showDebugPanel const showDebugPanel = settings.context.app.show_debug_panel
const paneCallbackProps = useMemo( const paneCallbackProps = useMemo(
() => ({ () => ({

View File

@ -79,11 +79,8 @@ const ProjectsContextDesktop = ({
} = useSettingsAuthContext() } = useSettingsAuthContext()
useEffect(() => { useEffect(() => {
console.log( console.log('project directory changed', settings.project.directory.current)
'project directory changed', }, [settings.project.directory.current])
settings.app.projectDirectory.current
)
}, [settings.app.projectDirectory.current])
const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0) const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)
const { projectPaths, projectsDir } = useProjectsLoader([ const { projectPaths, projectsDir } = useProjectsLoader([
@ -192,7 +189,7 @@ const ProjectsContextDesktop = ({
let name = ( let name = (
input && 'name' in input && input.name input && 'name' in input && input.name
? input.name ? input.name
: settings.projects.defaultProjectName.current : settings.project.default_project_name.current
).trim() ).trim()
if (doesProjectNameNeedInterpolated(name)) { if (doesProjectNameNeedInterpolated(name)) {
@ -257,8 +254,8 @@ const ProjectsContextDesktop = ({
{ {
input: { input: {
projects: projectPaths, projects: projectPaths,
defaultProjectName: settings.projects.defaultProjectName.current, defaultProjectName: settings.project.default_project_name.current,
defaultDirectory: settings.app.projectDirectory.current, defaultDirectory: settings.project.directory.current,
}, },
} }
) )

View File

@ -61,7 +61,7 @@ export const AllSettingsFields = forwardRef(
function restartOnboarding() { function restartOnboarding() {
send({ send({
type: `set.app.onboardingStatus`, type: `set.app.onboarding_status`,
data: { level: 'user', value: '' }, data: { level: 'user', value: '' },
}) })
} }
@ -73,7 +73,7 @@ export const AllSettingsFields = forwardRef(
useEffect(() => { useEffect(() => {
async function navigateToOnboardingStart() { async function navigateToOnboardingStart() {
if ( if (
state.context.app.onboardingStatus.user === '' && state.context.app.onboarding_status.user === '' &&
state.matches('idle') state.matches('idle')
) { ) {
if (isFileSettings) { if (isFileSettings) {

View File

@ -122,18 +122,20 @@ export const SettingsAuthProviderBase = ({
setClientSideSceneUnits: ({ context, event }) => { setClientSideSceneUnits: ({ context, event }) => {
const newBaseUnit = const newBaseUnit =
event.type === 'set.modeling.defaultUnit' event.type === 'set.modeling.base_unit'
? (event.data.value as BaseUnit) ? (event.data.value as BaseUnit)
: context.modeling.defaultUnit.current : context.modeling.base_unit.current
sceneInfra.baseUnit = newBaseUnit sceneInfra.baseUnit = newBaseUnit
}, },
setEngineTheme: ({ context }) => { setEngineTheme: ({ context }) => {
engineCommandManager engineCommandManager
.setTheme(context.app.theme.current) .setTheme(context.app.appearance.theme.current)
.catch(reportRejection) .catch(reportRejection)
}, },
setClientTheme: ({ context }) => { setClientTheme: ({ context }) => {
const opposingTheme = getOppositeTheme(context.app.theme.current) const opposingTheme = getOppositeTheme(
context.app.appearance.theme.current
)
sceneInfra.theme = opposingTheme sceneInfra.theme = opposingTheme
sceneEntitiesManager.updateSegmentBaseColor(opposingTheme) sceneEntitiesManager.updateSegmentBaseColor(opposingTheme)
}, },
@ -164,12 +166,12 @@ export const SettingsAuthProviderBase = ({
try { try {
const relevantSetting = (s: typeof settings) => { const relevantSetting = (s: typeof settings) => {
return ( return (
s.modeling?.defaultUnit?.current !== s.modeling?.base_unit.current !==
context.modeling.defaultUnit.current || context.modeling.base_unit.current ||
s.modeling.showScaleGrid.current !== s.modeling.show_scale_grid.current !==
context.modeling.showScaleGrid.current || context.modeling.show_scale_grid.current ||
s.modeling?.highlightEdges.current !== s.modeling?.highlight_edges.current !==
context.modeling.highlightEdges.current context.modeling.highlight_edges.current
) )
} }
@ -180,9 +182,9 @@ export const SettingsAuthProviderBase = ({
event.type === 'Reset settings' && relevantSetting(settings) event.type === 'Reset settings' && relevantSetting(settings)
if ( if (
event.type === 'set.modeling.defaultUnit' || event.type === 'set.modeling.base_unit' ||
event.type === 'set.modeling.showScaleGrid' || event.type === 'set.modeling.show_scale_grid' ||
event.type === 'set.modeling.highlightEdges' || event.type === 'set.modeling.highlight_edges' ||
allSettingsIncludesUnitChange || allSettingsIncludesUnitChange ||
resetSettingsIncludesUnitChange resetSettingsIncludesUnitChange
) { ) {
@ -258,7 +260,7 @@ export const SettingsAuthProviderBase = ({
useEffect(() => { useEffect(() => {
// If the user wants to hide the settings commands // If the user wants to hide the settings commands
//from the command bar don't add them. //from the command bar don't add them.
if (settingsState.context.commandBar.includeSettings.current === false) if (settingsState.context.command_bar.include_settings.current === false)
return return
const commands = settingsWithCommandConfigs(settingsState.context) const commands = settingsWithCommandConfigs(settingsState.context)
@ -334,7 +336,8 @@ export const SettingsAuthProviderBase = ({
// events outside of the machine that also depend on the machine's context // events outside of the machine that also depend on the machine's context
useEffect(() => { useEffect(() => {
const listener = (e: MediaQueryListEvent) => { const listener = (e: MediaQueryListEvent) => {
if (settingsState.context.app.theme.current !== 'system') return if (settingsState.context.app.appearance.theme.current !== 'system')
return
setThemeClass(e.matches ? Themes.Dark : Themes.Light) setThemeClass(e.matches ? Themes.Dark : Themes.Light)
} }
@ -349,9 +352,9 @@ export const SettingsAuthProviderBase = ({
useEffect(() => { useEffect(() => {
document.documentElement.style.setProperty( document.documentElement.style.setProperty(
`--primary-hue`, `--primary-hue`,
settingsState.context.app.themeColor.current settingsState.context.app.appearance.color.current
) )
}, [settingsState.context.app.themeColor.current]) }, [settingsState.context.app.appearance.color.current])
/** /**
* Update the --cursor-color CSS variable * Update the --cursor-color CSS variable
@ -360,11 +363,11 @@ export const SettingsAuthProviderBase = ({
useEffect(() => { useEffect(() => {
document.documentElement.style.setProperty( document.documentElement.style.setProperty(
`--cursor-color`, `--cursor-color`,
settingsState.context.textEditor.blinkingCursor.current settingsState.context.text_editor.blinking_cursor.current
? 'auto' ? 'auto'
: 'transparent' : 'transparent'
) )
}, [settingsState.context.textEditor.blinkingCursor.current]) }, [settingsState.context.text_editor.blinking_cursor.current])
// Auth machine setup // Auth machine setup
const [authState, authSend, authActor] = useMachine( const [authState, authSend, authActor] = useMachine(

View File

@ -41,7 +41,7 @@ export const Stream = () => {
const [streamState, setStreamState] = useState(StreamState.Unset) const [streamState, setStreamState] = useState(StreamState.Unset)
const { file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData const { file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const IDLE = settings.context.app.streamIdleMode.current const IDLE = settings.context.app.stream_idle_mode.current
const isNetworkOkay = const isNetworkOkay =
overallState === NetworkHealthState.Ok || overallState === NetworkHealthState.Ok ||
@ -342,7 +342,7 @@ export const Stream = () => {
id="video-stream" id="video-stream"
/> />
<ClientSideScene <ClientSideScene
cameraControls={settings.context.modeling.mouseControls.current} cameraControls={settings.context.modeling.mouse_controls.current}
/> />
{(streamState === StreamState.Paused || {(streamState === StreamState.Paused ||
streamState === StreamState.Resuming) && ( streamState === StreamState.Resuming) && (

View File

@ -18,7 +18,7 @@ export function UnitsMenu() {
<div className="absolute w-[1px] h-[1em] bg-primary right-0 top-1/2 -translate-y-1/2"></div> <div className="absolute w-[1px] h-[1em] bg-primary right-0 top-1/2 -translate-y-1/2"></div>
</div> </div>
<span className="sr-only">Current units are:&nbsp;</span> <span className="sr-only">Current units are:&nbsp;</span>
{settings.context.modeling.defaultUnit.current} {settings.context.modeling.base_unit.current}
</Popover.Button> </Popover.Button>
<Popover.Panel <Popover.Panel
className={`absolute bottom-full right-0 mb-2 w-48 bg-chalkboard-10 dark:bg-chalkboard-90 className={`absolute bottom-full right-0 mb-2 w-48 bg-chalkboard-10 dark:bg-chalkboard-90
@ -32,7 +32,7 @@ export function UnitsMenu() {
className="flex items-center gap-2 m-0 py-1.5 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left" className="flex items-center gap-2 m-0 py-1.5 px-2 cursor-pointer hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 border-none text-left"
onClick={() => { onClick={() => {
settings.send({ settings.send({
type: 'set.modeling.defaultUnit', type: 'set.modeling.base_unit',
data: { data: {
level: 'project', level: 'project',
value: unit, value: unit,
@ -42,7 +42,7 @@ export function UnitsMenu() {
}} }}
> >
<span className="flex-1">{baseUnitLabels[unit]}</span> <span className="flex-1">{baseUnitLabels[unit]}</span>
{unit === settings.context.modeling.defaultUnit.current && ( {unit === settings.context.modeling.base_unit.current && (
<span className="text-chalkboard-60">current</span> <span className="text-chalkboard-60">current</span>
)} )}
</button> </button>

View File

@ -10,7 +10,7 @@ export function useResolvedTheme() {
const { const {
settings: { context }, settings: { context },
} = useSettingsAuthContext() } = useSettingsAuthContext()
return context.app.theme.current === Themes.System return context.app.appearance.theme.current === Themes.System
? getSystemTheme() ? getSystemTheme()
: context.app.theme.current : context.app.appearance.theme.current
} }

View File

@ -63,9 +63,7 @@ export async function renameProjectDirectory(
export async function ensureProjectDirectoryExists( export async function ensureProjectDirectoryExists(
config: DeepPartial<Configuration> config: DeepPartial<Configuration>
): Promise<string | undefined> { ): Promise<string | undefined> {
const projectDir = const projectDir = config.settings?.project?.directory
config.settings?.app?.project_directory ||
config.settings?.project?.directory
if (!projectDir) { if (!projectDir) {
console.error('projectDir is falsey', config) console.error('projectDir is falsey', config)
return Promise.reject(new Error('projectDir is falsey')) return Promise.reject(new Error('projectDir is falsey'))
@ -520,8 +518,7 @@ export const readAppSettingsFile = async () => {
} }
const hasProjectDirectorySetting = const hasProjectDirectorySetting =
parsedAppConfig.settings?.project?.directory || parsedAppConfig.settings?.project?.directory
parsedAppConfig.settings?.app?.project_directory
if (hasProjectDirectorySetting) { if (hasProjectDirectorySetting) {
return parsedAppConfig return parsedAppConfig

View File

@ -86,8 +86,7 @@ export function kclCommands(
sampleName: data.sample, sampleName: data.sample,
code, code,
method: data.method, method: data.method,
sampleUnits: sampleUnits: projectSettingsPayload.modeling?.base_unit || 'mm',
projectSettingsPayload.modeling?.defaultUnit || 'mm',
} }
} }
) )

View File

@ -55,7 +55,7 @@ export const telemetryLoader: LoaderFunction = async ({
export const onboardingRedirectLoader: ActionFunction = async (args) => { export const onboardingRedirectLoader: ActionFunction = async (args) => {
const { settings } = await loadAndValidateSettings() const { settings } = await loadAndValidateSettings()
const onboardingStatus: OnboardingStatus = const onboardingStatus: OnboardingStatus =
settings.app.onboardingStatus.current || '' settings.app.onboarding_status.current || ''
const notEnRouteToOnboarding = !args.request.url.includes( const notEnRouteToOnboarding = !args.request.url.includes(
PATHS.ONBOARDING.INDEX PATHS.ONBOARDING.INDEX
) )

View File

@ -128,6 +128,7 @@ export function createSettings() {
/** /**
* The overall appearance of the app: light, dark, or system * The overall appearance of the app: light, dark, or system
*/ */
appearance: {
theme: new Setting<Themes>({ theme: new Setting<Themes>({
hideOnLevel: 'project', hideOnLevel: 'project',
defaultValue: Themes.System, defaultValue: Themes.System,
@ -148,7 +149,7 @@ export function createSettings() {
})), })),
}, },
}), }),
themeColor: new Setting<string>({ color: new Setting<string>({
defaultValue: '264.5', defaultValue: '264.5',
description: 'The hue of the primary theme color for the app', description: 'The hue of the primary theme color for the app',
validate: (v) => Number(v) >= 0 && Number(v) < 360, validate: (v) => Number(v) >= 0 && Number(v) < 360,
@ -172,17 +173,23 @@ export function createSettings() {
</div> </div>
), ),
}), }),
enableSSAO: new Setting<boolean>({ },
defaultValue: true, /**
description: * Whether to show the debug panel, which lets you see
'Whether or not Screen Space Ambient Occlusion (SSAO) is enabled', * various states of the app to aid in development
*/
show_debug_panel: new Setting<boolean>({
defaultValue: false,
description: 'Whether to show the debug panel, a development tool',
validate: (v) => typeof v === 'boolean', validate: (v) => typeof v === 'boolean',
hideOnPlatform: 'both', //for now commandConfig: {
inputType: 'boolean',
},
}), }),
/** /**
* Stream resource saving behavior toggle * Stream resource saving behavior toggle
*/ */
streamIdleMode: new Setting<boolean>({ stream_idle_mode: new Setting<boolean>({
defaultValue: false, defaultValue: false,
description: 'Toggle stream idling, saving bandwidth and battery', description: 'Toggle stream idling, saving bandwidth and battery',
validate: (v) => typeof v === 'boolean', validate: (v) => typeof v === 'boolean',
@ -190,7 +197,7 @@ export function createSettings() {
inputType: 'boolean', inputType: 'boolean',
}, },
}), }),
onboardingStatus: new Setting<OnboardingStatus>({ onboarding_status: new Setting<OnboardingStatus>({
defaultValue: '', defaultValue: '',
// TODO: this could be better but we don't have a TS side real enum // TODO: this could be better but we don't have a TS side real enum
// for this yet // for this yet
@ -198,62 +205,13 @@ export function createSettings() {
hideOnPlatform: 'both', hideOnPlatform: 'both',
}), }),
/** Permanently dismiss the banner warning to download the desktop app. */ /** Permanently dismiss the banner warning to download the desktop app. */
dismissWebBanner: new Setting<boolean>({ dismiss_web_banner: new Setting<boolean>({
defaultValue: false, defaultValue: false,
description: description:
'Permanently dismiss the banner warning to download the desktop app.', 'Permanently dismiss the banner warning to download the desktop app.',
validate: (v) => typeof v === 'boolean', validate: (v) => typeof v === 'boolean',
hideOnPlatform: 'desktop', hideOnPlatform: 'desktop',
}), }),
projectDirectory: new Setting<string>({
defaultValue: '',
description: 'The directory to save and load projects from',
hideOnLevel: 'project',
hideOnPlatform: 'web',
validate: (v) =>
typeof v === 'string' && (v.length > 0 || !isDesktop()),
Component: ({ value, updateValue }) => {
const inputRef = useRef<HTMLInputElement>(null)
return (
<div className="flex gap-4 p-1 border rounded-sm border-chalkboard-30">
<input
className="flex-grow text-xs px-2 bg-transparent"
value={value}
disabled
data-testid="project-directory-input"
ref={inputRef}
/>
<button
onClick={toSync(async () => {
// In desktop end-to-end tests we can't control the file picker,
// so we seed the new directory value in the element's dataset
const inputRefVal = inputRef.current?.dataset.testValue
if (
inputRef.current &&
inputRefVal &&
!Array.isArray(inputRefVal)
) {
updateValue(inputRefVal)
} else {
const newPath = await window.electron.open({
properties: ['openDirectory', 'createDirectory'],
defaultPath: value,
title: 'Choose a new project directory',
})
if (newPath.canceled) return
updateValue(newPath.filePaths[0])
}
}, reportRejection)}
className="p-0 m-0 border-none hover:bg-primary/10 focus:bg-primary/10 dark:hover:bg-primary/20 dark:focus::bg-primary/20"
data-testid="project-directory-button"
>
<CustomIcon name="folder" className="w-5 h-5" />
<Tooltip position="top-right">Choose a folder</Tooltip>
</button>
</div>
)
},
}),
}, },
/** /**
* Settings that affect the behavior while modeling. * Settings that affect the behavior while modeling.
@ -262,7 +220,7 @@ export function createSettings() {
/** /**
* The default unit to use in modeling dimensions * The default unit to use in modeling dimensions
*/ */
defaultUnit: new Setting<BaseUnit>({ base_unit: new Setting<BaseUnit>({
defaultValue: 'mm', defaultValue: 'mm',
description: 'The default unit to use in modeling dimensions', description: 'The default unit to use in modeling dimensions',
validate: (v) => baseUnitsUnion.includes(v as BaseUnit), validate: (v) => baseUnitsUnion.includes(v as BaseUnit),
@ -285,7 +243,7 @@ export function createSettings() {
/** /**
* The controls for how to navigate the 3D view * The controls for how to navigate the 3D view
*/ */
mouseControls: new Setting<CameraSystem>({ mouse_controls: new Setting<CameraSystem>({
defaultValue: 'Zoo', defaultValue: 'Zoo',
description: 'The controls for how to navigate the 3D view', description: 'The controls for how to navigate the 3D view',
validate: (v) => cameraSystems.includes(v as CameraSystem), validate: (v) => cameraSystems.includes(v as CameraSystem),
@ -345,7 +303,7 @@ export function createSettings() {
/** /**
* Projection method applied to the 3D view, perspective or orthographic * Projection method applied to the 3D view, perspective or orthographic
*/ */
cameraProjection: new Setting<CameraProjectionType>({ camera_projection: new Setting<CameraProjectionType>({
defaultValue: 'orthographic', defaultValue: 'orthographic',
hideOnLevel: 'project', hideOnLevel: 'project',
description: description:
@ -372,10 +330,17 @@ export function createSettings() {
})), })),
}, },
}), }),
enable_ssao: new Setting<boolean>({
defaultValue: true,
description:
'Whether or not Screen Space Ambient Occlusion (SSAO) is enabled',
validate: (v) => typeof v === 'boolean',
hideOnPlatform: 'both', //for now
}),
/** /**
* Whether to highlight edges of 3D objects * Whether to highlight edges of 3D objects
*/ */
highlightEdges: new Setting<boolean>({ highlight_edges: new Setting<boolean>({
defaultValue: true, defaultValue: true,
description: 'Whether to highlight edges of 3D objects', description: 'Whether to highlight edges of 3D objects',
validate: (v) => typeof v === 'boolean', validate: (v) => typeof v === 'boolean',
@ -387,7 +352,7 @@ export function createSettings() {
/** /**
* Whether to show a scale grid in the 3D modeling view * Whether to show a scale grid in the 3D modeling view
*/ */
showScaleGrid: new Setting<boolean>({ show_scale_grid: new Setting<boolean>({
defaultValue: false, defaultValue: false,
description: 'Whether to show a scale grid in the 3D modeling view', description: 'Whether to show a scale grid in the 3D modeling view',
validate: (v) => typeof v === 'boolean', validate: (v) => typeof v === 'boolean',
@ -396,18 +361,6 @@ export function createSettings() {
}, },
hideOnLevel: 'project', hideOnLevel: 'project',
}), }),
/**
* Whether to show the debug panel, which lets you see
* various states of the app to aid in development
*/
showDebugPanel: new Setting<boolean>({
defaultValue: false,
description: 'Whether to show the debug panel, a development tool',
validate: (v) => typeof v === 'boolean',
commandConfig: {
inputType: 'boolean',
},
}),
/** /**
* TODO: This setting is not yet implemented. * TODO: This setting is not yet implemented.
* Whether to turn off animations and other motion effects * Whether to turn off animations and other motion effects
@ -438,11 +391,11 @@ export function createSettings() {
/** /**
* Settings that affect the behavior of the KCL text editor. * Settings that affect the behavior of the KCL text editor.
*/ */
textEditor: { text_editor: {
/** /**
* Whether to wrap text in the editor or overflow with scroll * Whether to wrap text in the editor or overflow with scroll
*/ */
textWrapping: new Setting<boolean>({ text_wrapping: new Setting<boolean>({
defaultValue: true, defaultValue: true,
description: description:
'Whether to wrap text in the editor or overflow with scroll', 'Whether to wrap text in the editor or overflow with scroll',
@ -454,7 +407,7 @@ export function createSettings() {
/** /**
* Whether to make the cursor blink in the editor * Whether to make the cursor blink in the editor
*/ */
blinkingCursor: new Setting<boolean>({ blinking_cursor: new Setting<boolean>({
defaultValue: true, defaultValue: true,
description: 'Whether to make the cursor blink in the editor', description: 'Whether to make the cursor blink in the editor',
validate: (v) => typeof v === 'boolean', validate: (v) => typeof v === 'boolean',
@ -466,11 +419,60 @@ export function createSettings() {
/** /**
* Settings that affect the behavior of project management. * Settings that affect the behavior of project management.
*/ */
projects: { project: {
directory: new Setting<string>({
defaultValue: '',
description: 'The directory to save and load projects from',
hideOnLevel: 'project',
hideOnPlatform: 'web',
validate: (v) =>
typeof v === 'string' && (v.length > 0 || !isDesktop()),
Component: ({ value, updateValue }) => {
const inputRef = useRef<HTMLInputElement>(null)
return (
<div className="flex gap-4 p-1 border rounded-sm border-chalkboard-30">
<input
className="flex-grow text-xs px-2 bg-transparent"
value={value}
disabled
data-testid="project-directory-input"
ref={inputRef}
/>
<button
onClick={toSync(async () => {
// In desktop end-to-end tests we can't control the file picker,
// so we seed the new directory value in the element's dataset
const inputRefVal = inputRef.current?.dataset.testValue
if (
inputRef.current &&
inputRefVal &&
!Array.isArray(inputRefVal)
) {
updateValue(inputRefVal)
} else {
const newPath = await window.electron.open({
properties: ['openDirectory', 'createDirectory'],
defaultPath: value,
title: 'Choose a new project directory',
})
if (newPath.canceled) return
updateValue(newPath.filePaths[0])
}
}, reportRejection)}
className="p-0 m-0 border-none hover:bg-primary/10 focus:bg-primary/10 dark:hover:bg-primary/20 dark:focus::bg-primary/20"
data-testid="project-directory-button"
>
<CustomIcon name="folder" className="w-5 h-5" />
<Tooltip position="top-right">Choose a folder</Tooltip>
</button>
</div>
)
},
}),
/** /**
* The default project name to use when creating a new project * The default project name to use when creating a new project
*/ */
defaultProjectName: new Setting<string>({ default_project_name: new Setting<string>({
defaultValue: DEFAULT_PROJECT_NAME, defaultValue: DEFAULT_PROJECT_NAME,
description: description:
'The default project name to use when creating a new project', 'The default project name to use when creating a new project',
@ -504,11 +506,11 @@ export function createSettings() {
/** /**
* Settings that affect the behavior of the command bar. * Settings that affect the behavior of the command bar.
*/ */
commandBar: { command_bar: {
/** /**
* Whether to include settings in the command bar * Whether to include settings in the command bar
*/ */
includeSettings: new Setting<boolean>({ include_settings: new Setting<boolean>({
defaultValue: true, defaultValue: true,
description: 'Whether to include settings in the command bar', description: 'Whether to include settings in the command bar',
validate: (v) => typeof v === 'boolean', validate: (v) => typeof v === 'boolean',

View File

@ -26,8 +26,8 @@ describe(`testing settings initialization`, () => {
setSettingsAtLevel(settings, 'user', appSettingsPayload) setSettingsAtLevel(settings, 'user', appSettingsPayload)
expect(settings.app.theme.current).toBe('dark') expect(settings.app.appearance.theme.current).toBe('dark')
expect(settings.app.themeColor.current).toBe('190') expect(settings.app.appearance.color.current).toBe('190')
}) })
it(`doesn't read theme from project settings`, () => { it(`doesn't read theme from project settings`, () => {
@ -61,9 +61,9 @@ describe(`testing settings initialization`, () => {
setSettingsAtLevel(settings, 'project', projectSettingsPayload) setSettingsAtLevel(settings, 'project', projectSettingsPayload)
// The 'project'-level for `theme` setting should be ignored completely // The 'project'-level for `theme` setting should be ignored completely
expect(settings.app.theme.current).toBe('dark') expect(settings.app.appearance.theme.current).toBe('dark')
// But the 'project'-level for `themeColor` setting should be applied // But the 'project'-level for `themeColor` setting should be applied
expect(settings.app.themeColor.current).toBe('200') expect(settings.app.appearance.color.current).toBe('200')
}) })
}) })
@ -106,8 +106,8 @@ describe(`testing getAllCurrentSettings`, () => {
const allCurrentSettings = getAllCurrentSettings(settings) const allCurrentSettings = getAllCurrentSettings(settings)
// This one gets the 'user'-level theme because it's ignored at the project level // This one gets the 'user'-level theme because it's ignored at the project level
// (see the test "doesn't read theme from project settings") // (see the test "doesn't read theme from project settings")
expect(allCurrentSettings.app.theme).toBe('dark') expect(allCurrentSettings.app.appearance?.theme).toBe('dark')
expect(allCurrentSettings.app.themeColor).toBe('200') expect(allCurrentSettings.app.appearance?.color).toBe('200')
expect(allCurrentSettings.modeling.defaultUnit).toBe('ft') expect(allCurrentSettings.modeling.base_unit).toBe('ft')
}) })
}) })

View File

@ -34,36 +34,38 @@ export function configurationToSettingsPayload(
): DeepPartial<SaveSettingsPayload> { ): DeepPartial<SaveSettingsPayload> {
return { return {
app: { app: {
appearance: {
theme: appThemeToTheme(configuration?.settings?.app?.appearance?.theme), theme: appThemeToTheme(configuration?.settings?.app?.appearance?.theme),
themeColor: configuration?.settings?.app?.appearance?.color color: configuration?.settings?.app?.appearance?.color
? configuration?.settings?.app?.appearance?.color.toString() ? configuration?.settings?.app?.appearance?.color.toString()
: undefined, : undefined,
onboardingStatus: configuration?.settings?.app?.onboarding_status, },
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner, onboarding_status: configuration?.settings?.app?.onboarding_status,
streamIdleMode: configuration?.settings?.app?.stream_idle_mode, dismiss_web_banner: configuration?.settings?.app?.dismiss_web_banner,
projectDirectory: configuration?.settings?.project?.directory, stream_idle_mode: configuration?.settings?.app?.stream_idle_mode,
enableSSAO: configuration?.settings?.modeling?.enable_ssao, show_debug_panel: configuration?.settings?.app?.show_debug_panel,
}, },
modeling: { modeling: {
defaultUnit: configuration?.settings?.modeling?.base_unit, base_unit: configuration?.settings?.modeling?.base_unit,
cameraProjection: configuration?.settings?.modeling?.camera_projection, camera_projection: configuration?.settings?.modeling?.camera_projection,
mouseControls: mouseControlsToCameraSystem( mouse_controls: mouseControlsToCameraSystem(
configuration?.settings?.modeling?.mouse_controls configuration?.settings?.modeling?.mouse_controls
), ),
highlightEdges: configuration?.settings?.modeling?.highlight_edges, highlight_edges: configuration?.settings?.modeling?.highlight_edges,
showDebugPanel: configuration?.settings?.modeling?.show_debug_panel, show_scale_grid: configuration?.settings?.modeling?.show_scale_grid,
showScaleGrid: configuration?.settings?.modeling?.show_scale_grid, enable_ssao: configuration?.settings?.modeling?.enable_ssao,
}, },
textEditor: { text_editor: {
textWrapping: configuration?.settings?.text_editor?.text_wrapping, text_wrapping: configuration?.settings?.text_editor?.text_wrapping,
blinkingCursor: configuration?.settings?.text_editor?.blinking_cursor, blinking_cursor: configuration?.settings?.text_editor?.blinking_cursor,
}, },
projects: { project: {
defaultProjectName: default_project_name:
configuration?.settings?.project?.default_project_name, configuration?.settings?.project?.default_project_name,
directory: configuration?.settings?.project?.directory,
}, },
commandBar: { command_bar: {
includeSettings: configuration?.settings?.command_bar?.include_settings, include_settings: configuration?.settings?.command_bar?.include_settings,
}, },
} }
} }
@ -73,29 +75,28 @@ export function projectConfigurationToSettingsPayload(
): DeepPartial<SaveSettingsPayload> { ): DeepPartial<SaveSettingsPayload> {
return { return {
app: { app: {
appearance: {
// do not read in `theme`, because it is blocked on the project level // do not read in `theme`, because it is blocked on the project level
themeColor: configuration?.settings?.app?.appearance?.color color: configuration?.settings?.app?.appearance?.color
? configuration?.settings?.app?.appearance?.color.toString() ? configuration?.settings?.app?.appearance?.color.toString()
: undefined, : undefined,
onboardingStatus: configuration?.settings?.app?.onboarding_status, },
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner, onboarding_status: configuration?.settings?.app?.onboarding_status,
streamIdleMode: configuration?.settings?.app?.stream_idle_mode, dismiss_web_banner: configuration?.settings?.app?.dismiss_web_banner,
enableSSAO: configuration?.settings?.modeling?.enable_ssao, stream_idle_mode: configuration?.settings?.app?.stream_idle_mode,
show_debug_panel: configuration?.settings?.app?.show_debug_panel,
}, },
modeling: { modeling: {
defaultUnit: configuration?.settings?.modeling?.base_unit, base_unit: configuration?.settings?.modeling?.base_unit,
mouseControls: mouseControlsToCameraSystem( highlight_edges: configuration?.settings?.modeling?.highlight_edges,
configuration?.settings?.modeling?.mouse_controls enable_ssao: configuration?.settings?.modeling?.enable_ssao,
),
highlightEdges: configuration?.settings?.modeling?.highlight_edges,
showDebugPanel: configuration?.settings?.modeling?.show_debug_panel,
}, },
textEditor: { text_editor: {
textWrapping: configuration?.settings?.text_editor?.text_wrapping, text_wrapping: configuration?.settings?.text_editor?.text_wrapping,
blinkingCursor: configuration?.settings?.text_editor?.blinking_cursor, blinking_cursor: configuration?.settings?.text_editor?.blinking_cursor,
}, },
commandBar: { command_bar: {
includeSettings: configuration?.settings?.command_bar?.include_settings, include_settings: configuration?.settings?.command_bar?.include_settings,
}, },
} }
} }
@ -181,7 +182,7 @@ export async function loadAndValidateSettings(
// Because getting the default directory is async, we need to set it after // Because getting the default directory is async, we need to set it after
if (onDesktop) { if (onDesktop) {
settings.app.projectDirectory.default = await getInitialDefaultDir() settings.project.directory.default = await getInitialDefaultDir()
} }
settingsNext = setSettingsAtLevel( settingsNext = setSettingsAtLevel(

View File

@ -84,13 +84,13 @@ export const settingsMachine = setup({
return newContext return newContext
}), }),
setThemeClass: ({ context }) => { setThemeClass: ({ context }) => {
const currentTheme = context.app.theme.current ?? Themes.System const currentTheme = context.app.appearance.theme.current ?? Themes.System
setThemeClass( setThemeClass(
currentTheme === Themes.System ? getSystemTheme() : currentTheme currentTheme === Themes.System ? getSystemTheme() : currentTheme
) )
}, },
setEngineCameraProjection: ({ context }) => { setEngineCameraProjection: ({ context }) => {
const newCurrentProjection = context.modeling.cameraProjection.current const newCurrentProjection = context.modeling.camera_projection.current
sceneInfra.camControls.setEngineCameraProjection(newCurrentProjection) sceneInfra.camControls.setEngineCameraProjection(newCurrentProjection)
}, },
}, },

View File

@ -79,13 +79,13 @@ const Home = () => {
send({ send({
type: 'assign', type: 'assign',
data: { data: {
defaultProjectName: settings.projects.defaultProjectName.current, defaultProjectName: settings.project.default_project_name.current,
defaultDirectory: settings.app.projectDirectory.current, defaultDirectory: settings.project.directory.current,
}, },
}) })
}, [ }, [
settings.app.projectDirectory.current, settings.project.directory.current,
settings.projects.defaultProjectName.current, settings.project.default_project_name.current,
send, send,
]) ])
@ -134,7 +134,7 @@ const Home = () => {
groupId: 'projects', groupId: 'projects',
name: 'Create project', name: 'Create project',
argDefaultValues: { argDefaultValues: {
name: settings.projects.defaultProjectName.current, name: settings.project.default_project_name.current,
}, },
}, },
}) })
@ -207,7 +207,7 @@ const Home = () => {
to={`${PATHS.HOME + PATHS.SETTINGS_USER}#projectDirectory`} to={`${PATHS.HOME + PATHS.SETTINGS_USER}#projectDirectory`}
className="text-chalkboard-90 dark:text-chalkboard-20 underline underline-offset-2" className="text-chalkboard-90 dark:text-chalkboard-20 underline underline-offset-2"
> >
{settings.app.projectDirectory.current} {settings.project.directory.current}
</Link> </Link>
. .
</p> </p>

View File

@ -16,7 +16,7 @@ export default function Units() {
send, send,
state: { state: {
context: { context: {
modeling: { mouseControls }, modeling: { mouse_controls },
}, },
}, },
}, },
@ -38,10 +38,10 @@ export default function Units() {
<select <select
id="camera-controls" id="camera-controls"
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30" className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
value={mouseControls.current} value={mouse_controls.current}
onChange={(e) => { onChange={(e) => {
send({ send({
type: 'set.modeling.mouseControls', type: 'set.modeling.mouse_controls',
data: { data: {
level: 'user', level: 'user',
value: e.target.value as CameraSystem, value: e.target.value as CameraSystem,
@ -58,15 +58,15 @@ export default function Units() {
<ul className="mx-4 my-2 text-sm leading-relaxed"> <ul className="mx-4 my-2 text-sm leading-relaxed">
<li> <li>
<strong>Pan:</strong>{' '} <strong>Pan:</strong>{' '}
{cameraMouseDragGuards[mouseControls.current].pan.description} {cameraMouseDragGuards[mouse_controls.current].pan.description}
</li> </li>
<li> <li>
<strong>Zoom:</strong>{' '} <strong>Zoom:</strong>{' '}
{cameraMouseDragGuards[mouseControls.current].zoom.description} {cameraMouseDragGuards[mouse_controls.current].zoom.description}
</li> </li>
<li> <li>
<strong>Rotate:</strong>{' '} <strong>Rotate:</strong>{' '}
{cameraMouseDragGuards[mouseControls.current].rotate.description} {cameraMouseDragGuards[mouse_controls.current].rotate.description}
</li> </li>
</ul> </ul>
</SettingsSection> </SettingsSection>

View File

@ -126,7 +126,9 @@ function OnboardingIntroductionInner() {
settings: { settings: {
state: { state: {
context: { context: {
app: { theme }, app: {
appearance: { theme },
},
}, },
}, },
}, },

View File

@ -11,10 +11,12 @@ export default function OnboardingParametricModeling() {
settings: { settings: {
context: { context: {
app: { app: {
appearance: {
theme: { current: theme }, theme: { current: theme },
}, },
}, },
}, },
},
} = useSettingsAuthContext() } = useSettingsAuthContext()
const getImageTheme = () => const getImageTheme = () =>
theme === Themes.Light || theme === Themes.Light ||

View File

@ -13,7 +13,7 @@ export default function Units() {
settings: { settings: {
send, send,
context: { context: {
modeling: { defaultUnit }, modeling: { base_unit },
}, },
}, },
} = useSettingsAuthContext() } = useSettingsAuthContext()
@ -29,10 +29,10 @@ export default function Units() {
<select <select
id="base-unit" id="base-unit"
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent" className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
value={defaultUnit.user} value={base_unit.user}
onChange={(e) => { onChange={(e) => {
send({ send({
type: 'set.modeling.defaultUnit', type: 'set.modeling.base_unit',
data: { data: {
level: 'user', level: 'user',
value: e.target.value as BaseUnit, value: e.target.value as BaseUnit,

View File

@ -116,7 +116,7 @@ export function useNextClick(newStatus: string) {
return useCallback(() => { return useCallback(() => {
send({ send({
type: 'set.app.onboardingStatus', type: 'set.app.onboarding_status',
data: { level: 'user', value: newStatus }, data: { level: 'user', value: newStatus },
}) })
navigate(filePath + PATHS.ONBOARDING.INDEX.slice(0, -1) + newStatus) navigate(filePath + PATHS.ONBOARDING.INDEX.slice(0, -1) + newStatus)
@ -132,7 +132,7 @@ export function useDismiss() {
const settingsCallback = useCallback(() => { const settingsCallback = useCallback(() => {
send({ send({
type: 'set.app.onboardingStatus', type: 'set.app.onboarding_status',
data: { level: 'user', value: 'dismissed' }, data: { level: 'user', value: 'dismissed' },
}) })
}, [send]) }, [send])
@ -143,7 +143,7 @@ export function useDismiss() {
*/ */
useEffect(() => { useEffect(() => {
if ( if (
state.context.app.onboardingStatus.user === 'dismissed' && state.context.app.onboarding_status.user === 'dismissed' &&
state.matches('idle') state.matches('idle')
) { ) {
navigate(filePath) navigate(filePath)

View File

@ -26,7 +26,9 @@ const SignIn = () => {
settings: { settings: {
state: { state: {
context: { context: {
app: { theme }, app: {
appearance: { theme },
},
}, },
}, },
}, },

View File

@ -13,9 +13,7 @@ use tower_lsp::lsp_types::{
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation, MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
}; };
use crate::execution::Sketch; use crate::{execution::Sketch, std::Primitive};
use crate::std::Primitive;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
#[ts(export)] #[ts(export)]

View File

@ -2,6 +2,7 @@ use std::collections::HashMap;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use super::cad_op::{OpArg, Operation};
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{
@ -19,8 +20,6 @@ use crate::{
}, },
}; };
use super::cad_op::{OpArg, Operation};
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01; const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
impl BinaryPart { impl BinaryPart {

View File

@ -31,6 +31,9 @@ mod exec_ast;
mod function_param; mod function_param;
mod kcl_value; mod kcl_value;
// Re-exports.
pub use cad_op::Operation;
use crate::{ use crate::{
engine::{EngineManager, ExecutionKind}, engine::{EngineManager, ExecutionKind},
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
@ -47,9 +50,6 @@ use crate::{
ExecError, Program, ExecError, Program,
}; };
// Re-exports.
pub use cad_op::Operation;
/// State for executing a program. /// State for executing a program.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]

View File

@ -12,6 +12,7 @@ use winnow::{
token::{any, one_of, take_till}, token::{any, one_of, take_till},
}; };
use super::{ast::types::LabelledExpression, token::NumericSuffix};
use crate::{ use crate::{
docs::StdLibFn, docs::StdLibFn,
errors::{CompilationError, Severity, Tag}, errors::{CompilationError, Severity, Tag},
@ -33,8 +34,6 @@ use crate::{
SourceRange, SourceRange,
}; };
use super::{ast::types::LabelledExpression, token::NumericSuffix};
thread_local! { thread_local! {
/// The current `ParseContext`. `None` if parsing is not currently happening on this thread. /// The current `ParseContext`. `None` if parsing is not currently happening on this thread.
static CTXT: RefCell<Option<ParseContext>> = const { RefCell::new(None) }; static CTXT: RefCell<Option<ParseContext>> = const { RefCell::new(None) };

View File

@ -55,6 +55,11 @@ impl Configuration {
} }
} }
if settings.settings.modeling.show_debug_panel && !settings.settings.app.show_debug_panel {
settings.settings.app.show_debug_panel = settings.settings.modeling.show_debug_panel;
settings.settings.modeling.show_debug_panel = Default::default();
}
settings.validate()?; settings.validate()?;
Ok(settings) Ok(settings)
@ -121,6 +126,10 @@ pub struct AppSettings {
/// When the user is idle, and this is true, the stream will be torn down. /// When the user is idle, and this is true, the stream will be torn down.
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")] #[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
stream_idle_mode: bool, stream_idle_mode: bool,
/// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
pub show_debug_panel: bool,
} }
// TODO: When we remove backwards compatibility with the old settings file, we can remove this. // TODO: When we remove backwards compatibility with the old settings file, we can remove this.
@ -264,6 +273,7 @@ pub struct ModelingSettings {
pub highlight_edges: DefaultTrue, pub highlight_edges: DefaultTrue,
/// Whether to show the debug panel, which lets you see various states /// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development. /// of the app to aid in development.
/// Remove this when we remove backwards compatibility with the old settings file.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")] #[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
pub show_debug_panel: bool, pub show_debug_panel: bool,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled. /// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
@ -586,13 +596,14 @@ textWrapping = true
dismiss_web_banner: false, dismiss_web_banner: false,
enable_ssao: None, enable_ssao: None,
stream_idle_mode: false, stream_idle_mode: false,
show_debug_panel: true,
}, },
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::In, base_unit: UnitLength::In,
camera_projection: CameraProjectionType::Orthographic, camera_projection: CameraProjectionType::Orthographic,
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true, show_debug_panel: Default::default(),
enable_ssao: false.into(), enable_ssao: false.into(),
show_scale_grid: false, show_scale_grid: false,
}, },
@ -647,13 +658,14 @@ includeSettings = false
dismiss_web_banner: false, dismiss_web_banner: false,
enable_ssao: None, enable_ssao: None,
stream_idle_mode: false, stream_idle_mode: false,
show_debug_panel: true,
}, },
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::Yd, base_unit: UnitLength::Yd,
camera_projection: Default::default(), camera_projection: Default::default(),
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true, show_debug_panel: Default::default(),
enable_ssao: true.into(), enable_ssao: true.into(),
show_scale_grid: false, show_scale_grid: false,
}, },
@ -713,13 +725,14 @@ defaultProjectName = "projects-$nnn"
dismiss_web_banner: false, dismiss_web_banner: false,
enable_ssao: None, enable_ssao: None,
stream_idle_mode: false, stream_idle_mode: false,
show_debug_panel: true,
}, },
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::Yd, base_unit: UnitLength::Yd,
camera_projection: Default::default(), camera_projection: Default::default(),
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true, show_debug_panel: false,
enable_ssao: true.into(), enable_ssao: true.into(),
show_scale_grid: false, show_scale_grid: false,
}, },
@ -744,6 +757,7 @@ defaultProjectName = "projects-$nnn"
serialized, serialized,
r#"[settings.app] r#"[settings.app]
onboarding_status = "dismissed" onboarding_status = "dismissed"
show_debug_panel = true
[settings.app.appearance] [settings.app.appearance]
theme = "dark" theme = "dark"
@ -751,7 +765,6 @@ color = 138.0
[settings.modeling] [settings.modeling]
base_unit = "yd" base_unit = "yd"
show_debug_panel = true
[settings.text_editor] [settings.text_editor]
text_wrapping = false text_wrapping = false
@ -791,13 +804,14 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
dismiss_web_banner: false, dismiss_web_banner: false,
enable_ssao: None, enable_ssao: None,
stream_idle_mode: false, stream_idle_mode: false,
show_debug_panel: Default::default(),
}, },
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::Mm, base_unit: UnitLength::Mm,
camera_projection: Default::default(), camera_projection: Default::default(),
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: true.into(), highlight_edges: true.into(),
show_debug_panel: false, show_debug_panel: Default::default(),
enable_ssao: true.into(), enable_ssao: true.into(),
show_scale_grid: false, show_scale_grid: false,
}, },

View File

@ -5,8 +5,11 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
use crate::settings::types::{ use crate::{
AppColor, AppSettings, AppTheme, CommandBarSettings, ModelingSettings, TextEditorSettings, settings::types::{
is_default, AppColor, CommandBarSettings, DefaultTrue, FloatOrInt, OnboardingStatus, TextEditorSettings,
},
UnitLength,
}; };
/// High level project configuration. /// High level project configuration.
@ -24,14 +27,6 @@ impl ProjectConfiguration {
// TODO: remove this when we remove backwards compatibility with the old settings file. // TODO: remove this when we remove backwards compatibility with the old settings file.
pub fn backwards_compatible_toml_parse(toml_str: &str) -> Result<Self> { pub fn backwards_compatible_toml_parse(toml_str: &str) -> Result<Self> {
let mut settings = toml::from_str::<Self>(toml_str)?; let mut settings = toml::from_str::<Self>(toml_str)?;
settings.settings.app.project_directory = None;
if let Some(theme) = &settings.settings.app.theme {
if settings.settings.app.appearance.theme == AppTheme::default() {
settings.settings.app.appearance.theme = *theme;
settings.settings.app.theme = None;
}
}
if let Some(theme_color) = &settings.settings.app.theme_color { if let Some(theme_color) = &settings.settings.app.theme_color {
if settings.settings.app.appearance.color == AppColor::default() { if settings.settings.app.appearance.color == AppColor::default() {
@ -47,6 +42,11 @@ impl ProjectConfiguration {
} }
} }
if settings.settings.modeling.show_debug_panel && !settings.settings.app.show_debug_panel {
settings.settings.app.show_debug_panel = settings.settings.modeling.show_debug_panel;
settings.settings.modeling.show_debug_panel = Default::default();
}
settings.validate()?; settings.validate()?;
Ok(settings) Ok(settings)
@ -61,11 +61,11 @@ pub struct PerProjectSettings {
/// The settings for the modeling app. /// The settings for the modeling app.
#[serde(default)] #[serde(default)]
#[validate(nested)] #[validate(nested)]
pub app: AppSettings, pub app: ProjectAppSettings,
/// Settings that affect the behavior while modeling. /// Settings that affect the behavior while modeling.
#[serde(default)] #[serde(default)]
#[validate(nested)] #[validate(nested)]
pub modeling: ModelingSettings, pub modeling: ProjectModelingSettings,
/// Settings that affect the behavior of the KCL text editor. /// Settings that affect the behavior of the KCL text editor.
#[serde(default, alias = "textEditor")] #[serde(default, alias = "textEditor")]
#[validate(nested)] #[validate(nested)]
@ -76,15 +76,83 @@ pub struct PerProjectSettings {
pub command_bar: CommandBarSettings, pub command_bar: CommandBarSettings,
} }
/// Project application settings.
// TODO: When we remove backwards compatibility with the old settings file, we can remove the
// aliases to camelCase (and projects plural) from everywhere.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct ProjectAppSettings {
/// The settings for the appearance of the app.
#[serde(default, skip_serializing_if = "is_default")]
#[validate(nested)]
pub appearance: ProjectAppearanceSettings,
/// The onboarding status of the app.
#[serde(default, alias = "onboardingStatus", skip_serializing_if = "is_default")]
pub onboarding_status: OnboardingStatus,
/// The hue of the primary theme color for the app.
#[serde(default, skip_serializing_if = "Option::is_none", alias = "themeColor")]
pub theme_color: Option<FloatOrInt>,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
#[serde(default, alias = "enableSSAO", skip_serializing_if = "Option::is_none")]
pub enable_ssao: Option<bool>,
/// Permanently dismiss the banner warning to download the desktop app.
/// This setting only applies to the web app. And is temporary until we have Linux support.
#[serde(default, alias = "dismissWebBanner", skip_serializing_if = "is_default")]
pub dismiss_web_banner: bool,
/// When the user is idle, and this is true, the stream will be torn down.
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
stream_idle_mode: bool,
/// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
pub show_debug_panel: bool,
}
/// Per project appearance settings of the app.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
pub struct ProjectAppearanceSettings {
/// The hue of the primary theme color for the app.
#[serde(default, skip_serializing_if = "is_default")]
#[validate(nested)]
pub color: AppColor,
}
/// Per-project settings that affect the behavior while modeling.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Eq, Validate)]
#[serde(rename_all = "snake_case")]
#[ts(export)]
pub struct ProjectModelingSettings {
/// The default unit to use in modeling dimensions.
#[serde(default, alias = "defaultUnit", skip_serializing_if = "is_default")]
pub base_unit: UnitLength,
/// Highlight edges of 3D objects?
#[serde(default, alias = "highlightEdges", skip_serializing_if = "is_default")]
pub highlight_edges: DefaultTrue,
/// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development.
/// Remove this when we remove backwards compatibility with the old settings file.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
pub show_debug_panel: bool,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
#[serde(default, skip_serializing_if = "is_default")]
pub enable_ssao: DefaultTrue,
/// Whether or not to show a scale grid in the 3D modeling view
#[serde(default, alias = "showScaleGrid", skip_serializing_if = "is_default")]
pub show_scale_grid: bool,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::{ use super::{
AppSettings, AppTheme, CommandBarSettings, ModelingSettings, PerProjectSettings, ProjectConfiguration, CommandBarSettings, PerProjectSettings, ProjectAppSettings, ProjectAppearanceSettings, ProjectConfiguration,
TextEditorSettings, ProjectModelingSettings, TextEditorSettings,
}; };
use crate::settings::types::{AppearanceSettings, UnitLength}; use crate::settings::types::UnitLength;
#[test] #[test]
// Test that we can deserialize a project file from the old format. // Test that we can deserialize a project file from the old format.
@ -112,25 +180,19 @@ includeSettings = false
parsed, parsed,
ProjectConfiguration { ProjectConfiguration {
settings: PerProjectSettings { settings: PerProjectSettings {
app: AppSettings { app: ProjectAppSettings {
appearance: AppearanceSettings { appearance: ProjectAppearanceSettings { color: 138.0.into() },
theme: AppTheme::Dark,
color: 138.0.into()
},
onboarding_status: Default::default(), onboarding_status: Default::default(),
project_directory: None,
theme: None,
theme_color: None, theme_color: None,
dismiss_web_banner: false, dismiss_web_banner: false,
enable_ssao: None, enable_ssao: None,
stream_idle_mode: false, stream_idle_mode: false,
},
modeling: ModelingSettings {
base_unit: UnitLength::Yd,
camera_projection: Default::default(),
mouse_controls: Default::default(),
highlight_edges: Default::default(),
show_debug_panel: true, show_debug_panel: true,
},
modeling: ProjectModelingSettings {
base_unit: UnitLength::Yd,
highlight_edges: Default::default(),
show_debug_panel: Default::default(),
enable_ssao: true.into(), enable_ssao: true.into(),
show_scale_grid: false, show_scale_grid: false,
}, },
@ -144,6 +206,28 @@ includeSettings = false
} }
} }
); );
// Write the file back out.
let serialized = toml::to_string(&parsed).unwrap();
assert_eq!(
serialized,
r#"[settings.app]
show_debug_panel = true
[settings.app.appearance]
color = 138.0
[settings.modeling]
base_unit = "yd"
[settings.text_editor]
text_wrapping = false
blinking_cursor = false
[settings.command_bar]
include_settings = false
"#
);
} }
#[test] #[test]

View File

@ -3,14 +3,13 @@
use anyhow::Result; use anyhow::Result;
use derive_docs::stdlib; use derive_docs::stdlib;
use super::args::FromArgs;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ExecState, KclValue}, execution::{ExecState, KclValue},
std::Args, std::Args,
}; };
use super::args::FromArgs;
/// Compute the remainder after dividing `num` by `div`. /// Compute the remainder after dividing `num` by `div`.
/// If `num` is negative, the result will be too. /// If `num` is negative, the result will be too.
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {

View File

@ -2223,7 +2223,10 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use crate::{execution::TagIdentifier, std::sketch::PlaneData, std::utils::calculate_circle_center}; use crate::{
execution::TagIdentifier,
std::{sketch::PlaneData, utils::calculate_circle_center},
};
#[test] #[test]
fn test_deserialize_plane_data() { fn test_deserialize_plane_data() {