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:
@ -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' },
|
||||||
},
|
},
|
||||||
|
@ -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 (
|
||||||
|
@ -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'
|
||||||
|
@ -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"
|
||||||
|
@ -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'
|
||||||
|
Reference in New Issue
Block a user