Rearchitect settings system to be scoped (#1956)

* BROKEN: start of scopes for each setting

* Clean up later: mostly-functional scoped settings!
Broken command bar, unimplemented generated settings components

* Working persisted project settings in-folder

* Start working toward automatic commands and settings UI

* Relatively stable, settings-menu-editable

* Settings persistence tweaks after merge

* Custom settings UI working properly, cleaner types

* Allow boolean command types, create Settings UI for them

* Add support for option and string Settings input types

* Proof of concept settings from command bar

* Add all settings to command bar

* Allow settings to be hidden on a level

* Better command titles for settings

* Hide the settings the settings from the commands bar

* Derive command defaultValue from *current* settingsMachine context

* Fix generated settings UI for 'options' type settings

* Pretty settings modal 💅

* Allow for rollback to parent level setting

* fmt

* Fix tsc errors not related to loading from localStorage

* Better setting descriptions, better buttons

* Make displayName searchable in command bar

* Consolidate constants, get working in browser

* Start fixing tests, better types for saved settings payloads

* Fix playwright tests

* Add a test for the settings modal

* Add AtLeast to codespell ignore list

* Goofed merge of codespellrc

* Try fixing linux E2E tests

* Make codespellrc word lowercase

* fmt

* Fix data-testid in Tauri test

* Don't set text settings if nothing changed

* Turn off unimplemented settings

* Allow for multiple "execution-done" messages to have appeared in snapshot tests

* Try fixing up snapshot tests

* Switch from .json to .toml settings file format

* Use a different method for overriding the default units

* Try to force using the new common storage state in snapshot tests

* Update tests to use TOML

* fmt and remove console logs

* Restore units to export

* tsc errors, make snapshot tests use TOML

* Ensure that snapshot tests use the basicStorageState

* Re-organize use of test.use()

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Update snapshots one more time since lighting changed

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Fix broken "Show in folder" for project-level settings

* Fire all relevant actions after settings reset

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Properly reset the default directory

* Hide settings by platform

* Actually honor showDebugPanel

* Unify settings hiding logic

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* fix first extrusion snapshot

* another attempt to fix extrustion snapshot

* Rerun test suite

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* trigger CI

* more extrusion stuff

* Replace resetSettings console log with comment

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
This commit is contained in:
Frank Noirot
2024-04-02 10:29:34 -04:00
committed by GitHub
parent 77f51530f9
commit d605d4a029
67 changed files with 2470 additions and 1392 deletions

View File

@ -1,15 +1,18 @@
import { ActionFunction, LoaderFunction, redirect } from 'react-router-dom'
import { HomeLoaderData, IndexLoaderData } from './types'
import { FileLoaderData, HomeLoaderData, IndexLoaderData } from './types'
import { isTauri } from './isTauri'
import { paths } from './paths'
import { BROWSER_FILE_NAME } from 'Router'
import { SETTINGS_PERSIST_KEY } from 'lib/constants'
import { getProjectMetaByRouteId, paths } from './paths'
import { BROWSER_PATH } from 'lib/paths'
import {
BROWSER_FILE_NAME,
BROWSER_PROJECT_NAME,
PROJECT_ENTRYPOINT,
} from 'lib/constants'
import { loadAndValidateSettings } from './settings/settingsUtils'
import {
getInitialDefaultDir,
getProjectsInDir,
initializeProjectDirectory,
PROJECT_ENTRYPOINT,
} from './tauriFS'
import makeUrlPathRelative from './makeUrlPathRelative'
import { sep } from '@tauri-apps/api/path'
@ -20,17 +23,32 @@ import { fileSystemManager } from 'lang/std/fileSystemManager'
// The root loader simply resolves the settings and any errors that
// occurred during the settings load
export const indexLoader: LoaderFunction = async (): ReturnType<
typeof loadAndValidateSettings
> => {
return await loadAndValidateSettings()
export const settingsLoader: LoaderFunction = async ({
params,
}): ReturnType<typeof loadAndValidateSettings> => {
let settings = await loadAndValidateSettings()
// I don't love that we have to read the settings again here,
// but we need to get the project path to load the project settings
if (params.id) {
const defaultDir = settings.app.projectDirectory.current || ''
const projectPathData = getProjectMetaByRouteId(params.id, defaultDir)
if (projectPathData) {
const { projectPath } = projectPathData
settings = await loadAndValidateSettings(projectPath)
}
}
return settings
}
// Redirect users to the appropriate onboarding page if they haven't completed it
export const onboardingRedirectLoader: ActionFunction = async ({ request }) => {
const { settings } = await loadAndValidateSettings()
const onboardingStatus = settings.onboardingStatus || ''
const notEnRouteToOnboarding = !request.url.includes(paths.ONBOARDING.INDEX)
export const onboardingRedirectLoader: ActionFunction = async (args) => {
const settings = await loadAndValidateSettings()
const onboardingStatus = settings.app.onboardingStatus.current || ''
const notEnRouteToOnboarding = !args.request.url.includes(
paths.ONBOARDING.INDEX
)
// '' is the initial state, 'done' and 'dismissed' are the final states
const hasValidOnboardingStatus =
onboardingStatus.length === 0 ||
@ -44,34 +62,33 @@ export const onboardingRedirectLoader: ActionFunction = async ({ request }) => {
)
}
return null
return settingsLoader(args)
}
export const fileLoader: LoaderFunction = async ({
params,
}): Promise<IndexLoaderData | Response> => {
const { settings } = await loadAndValidateSettings()
}): Promise<FileLoaderData | Response> => {
let settings = await loadAndValidateSettings()
const defaultDir = settings.defaultDirectory || ''
const defaultDir = settings.app.projectDirectory.current || '/'
const projectPathData = getProjectMetaByRouteId(params.id, defaultDir)
const isBrowserProject = params.id === decodeURIComponent(BROWSER_PATH)
if (params.id && params.id !== BROWSER_FILE_NAME) {
const decodedId = decodeURIComponent(params.id)
const projectAndFile = decodedId.replace(defaultDir + sep, '')
const firstSlashIndex = projectAndFile.indexOf(sep)
const projectName = projectAndFile.slice(0, firstSlashIndex)
const projectPath = defaultDir + sep + projectName
const currentFileName = projectAndFile.slice(firstSlashIndex + 1)
if (!isBrowserProject && projectPathData) {
const { projectName, projectPath, currentFileName, currentFilePath } =
projectPathData
if (firstSlashIndex === -1 || !currentFileName)
if (!currentFileName || !currentFilePath) {
return redirect(
`${paths.FILE}/${encodeURIComponent(
`${params.id}${sep}${PROJECT_ENTRYPOINT}`
`${params.id}${isTauri() ? sep : '/'}${PROJECT_ENTRYPOINT}`
)}`
)
}
// TODO: PROJECT_ENTRYPOINT is hardcoded
// until we support setting a project's entrypoint file
const code = await readTextFile(decodedId)
const code = await readTextFile(currentFilePath)
const entrypointMetadata = await metadata(
projectPath + sep + PROJECT_ENTRYPOINT
)
@ -82,7 +99,7 @@ export const fileLoader: LoaderFunction = async ({
// So that WASM gets an updated path for operations
fileSystemManager.dir = projectPath
return {
const projectData: IndexLoaderData = {
code,
project: {
name: projectName,
@ -92,13 +109,26 @@ export const fileLoader: LoaderFunction = async ({
},
file: {
name: currentFileName,
path: params.id,
path: currentFilePath,
},
}
return {
...projectData,
}
}
return {
code: '',
project: {
name: BROWSER_PROJECT_NAME,
path: '/' + BROWSER_PROJECT_NAME,
children: [],
},
file: {
name: BROWSER_FILE_NAME,
path: decodeURIComponent(BROWSER_PATH),
},
}
}
@ -108,23 +138,15 @@ export const homeLoader: LoaderFunction = async (): Promise<
HomeLoaderData | Response
> => {
if (!isTauri()) {
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
return redirect(paths.FILE + '/' + BROWSER_PROJECT_NAME)
}
const { settings } = await loadAndValidateSettings()
const settings = await loadAndValidateSettings()
const projectDir = await initializeProjectDirectory(
settings.defaultDirectory || (await getInitialDefaultDir())
settings.app.projectDirectory.current || (await getInitialDefaultDir())
)
if (projectDir.path) {
if (projectDir.path !== settings.defaultDirectory) {
localStorage.setItem(
SETTINGS_PERSIST_KEY,
JSON.stringify({
...settings,
defaultDirectory: projectDir,
})
)
}
const projects = await getProjectsInDir(projectDir.path)
return {