Make empty defaultProjectName value impossible (#409)

* Set named const as default project name

* Refactor: move base units into settings machine

Signed off by Frank Noirot <frank@kittycad.io>

* Reset default when creating with blank name

Signed off by Frank Noirot <frank@kittycad.io>

* Make it impossible to set empty defaultProjectName

Signed off by Frank Noirot <frank@kittycad.io>

* Make it impossible to assign empty strings
to defaultProjectName

Signed off by Frank Noirot <frank@kittycad.io>
This commit is contained in:
Frank Noirot
2023-09-07 21:48:51 -04:00
committed by GitHub
parent 3da6fc3b7e
commit 0120a89d9c
5 changed files with 49 additions and 18 deletions

View File

@ -1,13 +1,25 @@
import { assign, createMachine } from 'xstate' import { assign, createMachine } from 'xstate'
import { BaseUnit, baseUnitsUnion } from '../useStore'
import { CommandBarMeta } from '../lib/commands' import { CommandBarMeta } from '../lib/commands'
import { Themes, getSystemTheme, setThemeClass } from '../lib/theme' import { Themes, getSystemTheme, setThemeClass } from '../lib/theme'
export const DEFAULT_PROJECT_NAME = 'project-$nnn'
export enum UnitSystem { export enum UnitSystem {
Imperial = 'imperial', Imperial = 'imperial',
Metric = 'metric', Metric = 'metric',
} }
export const baseUnits = {
imperial: ['in', 'ft'],
metric: ['mm', 'cm', 'm'],
} as const
export type BaseUnit = 'in' | 'ft' | 'mm' | 'cm' | 'm'
export const baseUnitsUnion = Object.values(baseUnits).flatMap((v) => v)
export type Toggle = 'On' | 'Off'
export const SETTINGS_PERSIST_KEY = 'SETTINGS_PERSIST_KEY' export const SETTINGS_PERSIST_KEY = 'SETTINGS_PERSIST_KEY'
export const settingsCommandBarMeta: CommandBarMeta = { export const settingsCommandBarMeta: CommandBarMeta = {
@ -85,11 +97,11 @@ export const settingsMachine = createMachine(
predictableActionArguments: true, predictableActionArguments: true,
context: { context: {
theme: Themes.System, theme: Themes.System,
defaultProjectName: '', defaultProjectName: DEFAULT_PROJECT_NAME,
unitSystem: UnitSystem.Imperial, unitSystem: UnitSystem.Imperial,
baseUnit: 'in' as BaseUnit, baseUnit: 'in' as BaseUnit,
defaultDirectory: '', defaultDirectory: '',
textWrapping: 'On' as 'On' | 'Off', textWrapping: 'On' as Toggle,
showDebugPanel: false, showDebugPanel: false,
onboardingStatus: '', onboardingStatus: '',
}, },
@ -113,7 +125,8 @@ export const settingsMachine = createMachine(
'Set Default Project Name': { 'Set Default Project Name': {
actions: [ actions: [
assign({ assign({
defaultProjectName: (_, event) => event.data.defaultProjectName, defaultProjectName: (_, event) =>
event.data.defaultProjectName.trim() || DEFAULT_PROJECT_NAME,
}), }),
'persistSettings', 'persistSettings',
'toastSuccess', 'toastSuccess',
@ -205,7 +218,7 @@ export const settingsMachine = createMachine(
data: { unitSystem: UnitSystem } data: { unitSystem: UnitSystem }
} }
| { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } } | { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } }
| { type: 'Set Text Wrapping'; data: { textWrapping: 'On' | 'Off' } } | { type: 'Set Text Wrapping'; data: { textWrapping: Toggle } }
| { type: 'Set Onboarding Status'; data: { onboardingStatus: string } } | { type: 'Set Onboarding Status'; data: { onboardingStatus: string } }
| { type: 'Toggle Debug Panel' }, | { type: 'Toggle Debug Panel' },
}, },

View File

@ -28,6 +28,7 @@ import {
import useStateMachineCommands from '../hooks/useStateMachineCommands' import useStateMachineCommands from '../hooks/useStateMachineCommands'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { DEFAULT_PROJECT_NAME } from 'machines/settingsMachine'
// This route only opens in the Tauri desktop context for now, // This route only opens in the Tauri desktop context for now,
// as defined in Router.tsx, so we can use the Tauri APIs and types. // as defined in Router.tsx, so we can use the Tauri APIs and types.
@ -38,6 +39,7 @@ const Home = () => {
const { const {
settings: { settings: {
context: { defaultDirectory, defaultProjectName }, context: { defaultDirectory, defaultProjectName },
send: sendToSettings,
}, },
} = useGlobalStateContext() } = useGlobalStateContext()
@ -71,16 +73,33 @@ const Home = () => {
context: ContextFrom<typeof homeMachine>, context: ContextFrom<typeof homeMachine>,
event: EventFrom<typeof homeMachine, 'Create project'> event: EventFrom<typeof homeMachine, 'Create project'>
) => { ) => {
let name = let name = (
event.data && 'name' in event.data event.data && 'name' in event.data
? event.data.name ? event.data.name
: defaultProjectName : defaultProjectName
).trim()
let shouldUpdateDefaultProjectName = false
// If there is no default project name, flag it to be set to the default
if (!name) {
name = DEFAULT_PROJECT_NAME
shouldUpdateDefaultProjectName = true
}
if (doesProjectNameNeedInterpolated(name)) { if (doesProjectNameNeedInterpolated(name)) {
const nextIndex = await getNextProjectIndex(name, projects) const nextIndex = await getNextProjectIndex(name, projects)
name = interpolateProjectNameWithIndex(name, nextIndex) name = interpolateProjectNameWithIndex(name, nextIndex)
} }
await createNewProject(context.defaultDirectory + '/' + name) await createNewProject(context.defaultDirectory + '/' + name)
if (shouldUpdateDefaultProjectName) {
sendToSettings({
type: 'Set Default Project Name',
data: { defaultProjectName: DEFAULT_PROJECT_NAME },
})
}
return `Successfully created "${name}"` return `Successfully created "${name}"`
}, },
renameProject: async ( renameProject: async (

View File

@ -1,5 +1,5 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons' import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { BaseUnit, baseUnits } from '../../useStore' import { BaseUnit, baseUnits } from '../../machines/settingsMachine'
import { ActionButton } from '../../components/ActionButton' import { ActionButton } from '../../components/ActionButton'
import { SettingsSection } from '../Settings' import { SettingsSection } from '../Settings'
import { Toggle } from '../../components/Toggle/Toggle' import { Toggle } from '../../components/Toggle/Toggle'

View File

@ -6,7 +6,11 @@ import {
import { ActionButton } from '../components/ActionButton' import { ActionButton } from '../components/ActionButton'
import { AppHeader } from '../components/AppHeader' import { AppHeader } from '../components/AppHeader'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/api/dialog'
import { BaseUnit, baseUnits } from '../useStore' import {
BaseUnit,
DEFAULT_PROJECT_NAME,
baseUnits,
} from '../machines/settingsMachine'
import { Toggle } from '../components/Toggle/Toggle' import { Toggle } from '../components/Toggle/Toggle'
import { useLocation, useNavigate, useRouteLoaderData } from 'react-router-dom' import { useLocation, useNavigate, useRouteLoaderData } from 'react-router-dom'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
@ -118,10 +122,14 @@ export const Settings = () => {
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"
defaultValue={defaultProjectName} defaultValue={defaultProjectName}
onBlur={(e) => { onBlur={(e) => {
const newValue = e.target.value.trim() || DEFAULT_PROJECT_NAME
send({ send({
type: 'Set Default Project Name', type: 'Set Default Project Name',
data: { defaultProjectName: e.target.value }, data: {
defaultProjectName: newValue,
},
}) })
e.target.value = newValue
}} }}
autoCapitalize="off" autoCapitalize="off"
autoComplete="off" autoComplete="off"

View File

@ -94,15 +94,6 @@ export type GuiModes =
position: Position position: Position
} }
export const baseUnits = {
imperial: ['in', 'ft'],
metric: ['mm', 'cm', 'm'],
} as const
export type BaseUnit = 'in' | 'ft' | 'mm' | 'cm' | 'm'
export const baseUnitsUnion = Object.values(baseUnits).flatMap((v) => v)
export type PaneType = export type PaneType =
| 'code' | 'code'
| 'variables' | 'variables'