Make settings auto-save (#242)
* Feature: settings auto-save as they are updated * Refactor: get rid of temporary settings states * Feature: add escape hotkey to settings * Style: layout tweaks * Feature: setting unit system updates base unit too
This commit is contained in:
111
src/App.tsx
111
src/App.tsx
@ -9,12 +9,7 @@ import { DebugPanel } from './components/DebugPanel'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { asyncLexer } from './lang/tokeniser'
|
||||
import { abstractSyntaxTree } from './lang/abstractSyntaxTree'
|
||||
import {
|
||||
_executor,
|
||||
ProgramMemory,
|
||||
ExtrudeGroup,
|
||||
SketchGroup,
|
||||
} from './lang/executor'
|
||||
import { _executor } from './lang/executor'
|
||||
import CodeMirror from '@uiw/react-codemirror'
|
||||
import { langs } from '@uiw/codemirror-extensions-langs'
|
||||
import { linter, lintGutter } from '@codemirror/lint'
|
||||
@ -358,7 +353,7 @@ export function App() {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-screen relative flex flex-col"
|
||||
className="h-screen overflow-hidden relative flex flex-col"
|
||||
onMouseMove={handleMouseMove}
|
||||
>
|
||||
<AppHeader
|
||||
@ -371,7 +366,7 @@ export function App() {
|
||||
<ModalContainer />
|
||||
<Resizable
|
||||
className={
|
||||
'z-10 my-5 ml-5 pr-1 flex flex-col flex-grow overflow-hidden transition-opacity transition-duration-75 ' +
|
||||
'h-full flex flex-col flex-1 z-10 my-5 ml-5 pr-1 transition-opacity transition-duration-75 ' +
|
||||
(isMouseDownInStream || onboardingStatus === 'camera'
|
||||
? ' pointer-events-none '
|
||||
: ' ') +
|
||||
@ -390,57 +385,59 @@ export function App() {
|
||||
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
|
||||
}}
|
||||
>
|
||||
<CollapsiblePanel
|
||||
title="Code"
|
||||
icon={faCode}
|
||||
className="open:!mb-2"
|
||||
open={openPanes.includes('code')}
|
||||
>
|
||||
<div className="px-2 py-1">
|
||||
<button
|
||||
// disabled={!shouldFormat}
|
||||
onClick={formatCode}
|
||||
// className={`${!shouldFormat && 'text-gray-300'}`}
|
||||
>
|
||||
format
|
||||
</button>
|
||||
</div>
|
||||
<div id="code-mirror-override">
|
||||
<CodeMirror
|
||||
className="h-full"
|
||||
value={code}
|
||||
extensions={[
|
||||
langs.javascript({ jsx: true }),
|
||||
lineHighlightField,
|
||||
...extraExtensions,
|
||||
]}
|
||||
onChange={onChange}
|
||||
onUpdate={onUpdate}
|
||||
<div className="h-full flex flex-col justify-between">
|
||||
<CollapsiblePanel
|
||||
title="Code"
|
||||
icon={faCode}
|
||||
className="open:!mb-2"
|
||||
open={openPanes.includes('code')}
|
||||
>
|
||||
<div className="px-2 py-1">
|
||||
<button
|
||||
// disabled={!shouldFormat}
|
||||
onClick={formatCode}
|
||||
// className={`${!shouldFormat && 'text-gray-300'}`}
|
||||
>
|
||||
format
|
||||
</button>
|
||||
</div>
|
||||
<div id="code-mirror-override">
|
||||
<CodeMirror
|
||||
className="h-full"
|
||||
value={code}
|
||||
extensions={[
|
||||
langs.javascript({ jsx: true }),
|
||||
lineHighlightField,
|
||||
...extraExtensions,
|
||||
]}
|
||||
onChange={onChange}
|
||||
onUpdate={onUpdate}
|
||||
theme={theme}
|
||||
onCreateEditor={(_editorView) => setEditorView(_editorView)}
|
||||
/>
|
||||
</div>
|
||||
</CollapsiblePanel>
|
||||
<section className="flex flex-col">
|
||||
<MemoryPanel
|
||||
theme={theme}
|
||||
onCreateEditor={(_editorView) => setEditorView(_editorView)}
|
||||
open={openPanes.includes('variables')}
|
||||
title="Variables"
|
||||
icon={faSquareRootVariable}
|
||||
/>
|
||||
</div>
|
||||
</CollapsiblePanel>
|
||||
<section className="flex flex-col mt-auto">
|
||||
<MemoryPanel
|
||||
theme={theme}
|
||||
open={openPanes.includes('variables')}
|
||||
title="Variables"
|
||||
icon={faSquareRootVariable}
|
||||
/>
|
||||
<Logs
|
||||
theme={theme}
|
||||
open={openPanes.includes('logs')}
|
||||
title="Logs"
|
||||
icon={faCodeCommit}
|
||||
/>
|
||||
<KCLErrors
|
||||
theme={theme}
|
||||
open={openPanes.includes('kclErrors')}
|
||||
title="KCL Errors"
|
||||
iconClassNames={{ icon: 'group-open:text-destroy-30' }}
|
||||
/>
|
||||
</section>
|
||||
<Logs
|
||||
theme={theme}
|
||||
open={openPanes.includes('logs')}
|
||||
title="Logs"
|
||||
icon={faCodeCommit}
|
||||
/>
|
||||
<KCLErrors
|
||||
theme={theme}
|
||||
open={openPanes.includes('kclErrors')}
|
||||
title="KCL Errors"
|
||||
iconClassNames={{ icon: 'group-open:text-destroy-30' }}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</Resizable>
|
||||
<Stream className="absolute inset-0 z-0" />
|
||||
{debugPanel && (
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {
|
||||
faArrowRotateBack,
|
||||
faCheck,
|
||||
faFolder,
|
||||
faXmark,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
@ -8,27 +7,29 @@ import { ActionButton } from '../components/ActionButton'
|
||||
import { AppHeader } from '../components/AppHeader'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { baseUnits, useStore } from '../useStore'
|
||||
import { useState } from 'react'
|
||||
import { useRef } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { Toggle } from '../components/Toggle/Toggle'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
export const Settings = () => {
|
||||
const navigate = useNavigate()
|
||||
useHotkeys('esc', () => navigate('/'))
|
||||
const {
|
||||
defaultDir: ogDefaultDir,
|
||||
setDefaultDir: saveDefaultDir,
|
||||
defaultProjectName: ogDefaultProjectName,
|
||||
setDefaultProjectName: saveDefaultProjectName,
|
||||
defaultUnitSystem: ogDefaultUnitSystem,
|
||||
setDefaultUnitSystem: saveDefaultUnitSystem,
|
||||
defaultBaseUnit: ogDefaultBaseUnit,
|
||||
setDefaultBaseUnit: saveDefaultBaseUnit,
|
||||
saveDebugPanel,
|
||||
originalDebugPanel,
|
||||
defaultDir,
|
||||
setDefaultDir,
|
||||
defaultProjectName,
|
||||
setDefaultProjectName,
|
||||
defaultUnitSystem,
|
||||
setDefaultUnitSystem,
|
||||
defaultBaseUnit,
|
||||
setDefaultBaseUnit,
|
||||
setDebugPanel,
|
||||
debugPanel,
|
||||
setOnboardingStatus,
|
||||
theme: ogTheme,
|
||||
setTheme: saveTheme,
|
||||
theme,
|
||||
setTheme,
|
||||
} = useStore((s) => ({
|
||||
defaultDir: s.defaultDir,
|
||||
setDefaultDir: s.setDefaultDir,
|
||||
@ -38,20 +39,13 @@ export const Settings = () => {
|
||||
setDefaultUnitSystem: s.setDefaultUnitSystem,
|
||||
defaultBaseUnit: s.defaultBaseUnit,
|
||||
setDefaultBaseUnit: s.setDefaultBaseUnit,
|
||||
saveDebugPanel: s.setDebugPanel,
|
||||
originalDebugPanel: s.debugPanel,
|
||||
setDebugPanel: s.setDebugPanel,
|
||||
debugPanel: s.debugPanel,
|
||||
setOnboardingStatus: s.setOnboardingStatus,
|
||||
theme: s.theme,
|
||||
setTheme: s.setTheme,
|
||||
}))
|
||||
const [defaultDir, setDefaultDir] = useState(ogDefaultDir)
|
||||
const [defaultProjectName, setDefaultProjectName] =
|
||||
useState(ogDefaultProjectName)
|
||||
const [defaultUnitSystem, setDefaultUnitSystem] =
|
||||
useState(ogDefaultUnitSystem)
|
||||
const [defaultBaseUnit, setDefaultBaseUnit] = useState(ogDefaultBaseUnit)
|
||||
const [debugPanel, setDebugPanel] = useState(originalDebugPanel)
|
||||
const [theme, setTheme] = useState(ogTheme)
|
||||
const ogDefaultProjectName = useRef(defaultProjectName)
|
||||
|
||||
async function handleDirectorySelection() {
|
||||
const newDirectory = await open({
|
||||
@ -65,16 +59,6 @@ export const Settings = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveClick = () => {
|
||||
saveDefaultDir(defaultDir)
|
||||
saveDefaultProjectName(defaultProjectName)
|
||||
saveDefaultUnitSystem(defaultUnitSystem)
|
||||
saveDefaultBaseUnit(defaultBaseUnit)
|
||||
saveDebugPanel(debugPanel)
|
||||
saveTheme(theme)
|
||||
toast.success('Settings saved!')
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppHeader showToolbar={false}>
|
||||
@ -92,7 +76,7 @@ export const Settings = () => {
|
||||
Close
|
||||
</ActionButton>
|
||||
</AppHeader>
|
||||
<div className="mt-24 max-w-5xl mx-auto">
|
||||
<div className="mt-16 max-w-3xl mx-auto">
|
||||
<h1 className="text-4xl font-bold">User Settings</h1>
|
||||
{(window as any).__TAURI__ && (
|
||||
<SettingsSection
|
||||
@ -103,12 +87,13 @@ export const Settings = () => {
|
||||
<input
|
||||
className="flex-1 px-2 bg-transparent"
|
||||
value={defaultDir.dir}
|
||||
onChange={(e) =>
|
||||
onChange={(e) => {
|
||||
setDefaultDir({
|
||||
base: ogDefaultDir.base,
|
||||
base: defaultDir.base,
|
||||
dir: e.target.value,
|
||||
})
|
||||
}
|
||||
toast.success('Default directory updated')
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
@ -134,7 +119,13 @@ export const Settings = () => {
|
||||
<input
|
||||
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
||||
value={defaultProjectName}
|
||||
onChange={(e) => setDefaultProjectName(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setDefaultProjectName(e.target.value)
|
||||
}}
|
||||
onBlur={() => {
|
||||
ogDefaultProjectName.current !== defaultProjectName &&
|
||||
toast.success('Default project name updated')
|
||||
}}
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
@ -146,9 +137,12 @@ export const Settings = () => {
|
||||
onLabel="Metric"
|
||||
name="settings-units"
|
||||
checked={defaultUnitSystem === 'metric'}
|
||||
onChange={(e) =>
|
||||
setDefaultUnitSystem(e.target.checked ? 'metric' : 'imperial')
|
||||
}
|
||||
onChange={(e) => {
|
||||
const newUnitSystem = e.target.checked ? 'metric' : 'imperial'
|
||||
setDefaultUnitSystem(newUnitSystem)
|
||||
setDefaultBaseUnit(baseUnits[newUnitSystem][0])
|
||||
toast.success('Unit system set to ' + newUnitSystem)
|
||||
}}
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
@ -159,7 +153,10 @@ export const Settings = () => {
|
||||
id="base-unit"
|
||||
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
||||
value={defaultBaseUnit}
|
||||
onChange={(e) => setDefaultBaseUnit(e.target.value)}
|
||||
onChange={(e) => {
|
||||
setDefaultBaseUnit(e.target.value)
|
||||
toast.success('Base unit changed to ' + e.target.value)
|
||||
}}
|
||||
>
|
||||
{baseUnits[defaultUnitSystem].map((unit) => (
|
||||
<option key={unit} value={unit}>
|
||||
@ -175,7 +172,12 @@ export const Settings = () => {
|
||||
<Toggle
|
||||
name="settings-debug-panel"
|
||||
checked={debugPanel}
|
||||
onChange={(e) => setDebugPanel(e.target.checked)}
|
||||
onChange={(e) => {
|
||||
setDebugPanel(e.target.checked)
|
||||
toast.success(
|
||||
'Debug panel toggled ' + (e.target.checked ? 'on' : 'off')
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
@ -187,7 +189,15 @@ export const Settings = () => {
|
||||
offLabel="Dark"
|
||||
onLabel="Light"
|
||||
checked={theme === 'light'}
|
||||
onChange={(e) => setTheme(e.target.checked ? 'light' : 'dark')}
|
||||
onChange={(e) => {
|
||||
const newTheme = e.target.checked ? 'light' : 'dark'
|
||||
setTheme(newTheme)
|
||||
toast.success(
|
||||
newTheme.slice(0, 1).toLocaleUpperCase() +
|
||||
newTheme.slice(1) +
|
||||
' mode activated'
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</SettingsSection>
|
||||
<SettingsSection
|
||||
@ -204,19 +214,6 @@ export const Settings = () => {
|
||||
Replay Onboarding
|
||||
</ActionButton>
|
||||
</SettingsSection>
|
||||
<ActionButton
|
||||
className="hover:border-succeed-50"
|
||||
onClick={handleSaveClick}
|
||||
icon={{
|
||||
icon: faCheck,
|
||||
bgClassName:
|
||||
'bg-succeed-80 group-hover:bg-succeed-70 hover:bg-succeed-70',
|
||||
iconClassName:
|
||||
'text-succeed-20 group-hover:text-succeed-10 hover:text-succeed-10',
|
||||
}}
|
||||
>
|
||||
Save Settings
|
||||
</ActionButton>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@ -233,7 +230,7 @@ export function SettingsSection({
|
||||
children,
|
||||
}: SettingsSectionProps) {
|
||||
return (
|
||||
<section className="my-8 first-of-type:mt-16 last-of-type:mb-16 flex gap-12 items-start">
|
||||
<section className="my-16 last-of-type:mb-24 grid grid-cols-2 gap-12 items-start">
|
||||
<div className="w-80">
|
||||
<h2 className="text-2xl">{title}</h2>
|
||||
<p className="mt-2 text-sm">{description}</p>
|
||||
|
Reference in New Issue
Block a user