Add unit setting (#183)

* Add Toggle component

* Add default units to settings

* Add defaultBaseUnit, shorten settings names

* Make debug panel use Toggle, fix colors

* add eslint-plugin-css-modules
This commit is contained in:
Frank Noirot
2023-07-21 12:48:23 -04:00
committed by GitHub
parent 87aecf7f50
commit 48f1d5e623
9 changed files with 170 additions and 15 deletions

View File

@ -1,7 +1,11 @@
{ {
"plugins": [
"css-modules"
],
"extends": [ "extends": [
"react-app", "react-app",
"react-app/jest" "react-app/jest",
"plugin:css-modules/recommended"
], ],
"rules": { "rules": {
"semi": [ "semi": [

View File

@ -85,6 +85,7 @@
"babel-jest": "^29.6.1", "babel-jest": "^29.6.1",
"eslint": "^8.44.0", "eslint": "^8.44.0",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"eslint-plugin-css-modules": "^2.11.0",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"jest": "^29.6.1", "jest": "^29.6.1",
"jest-environment-jsdom": "^29.6.1", "jest-environment-jsdom": "^29.6.1",

View File

@ -0,0 +1,32 @@
.toggle {
@apply flex items-center gap-2 w-fit;
--toggle-size: 1.25rem;
--padding: 0.25rem;
}
.toggle:focus-within > span {
@apply outline-none ring-2;
}
.toggle input {
@apply sr-only;
}
.toggle > span {
@apply relative rounded border border-chalkboard-110;
width: calc(2 * (var(--toggle-size) + var(--padding)));
height: calc(var(--toggle-size) + var(--padding));
}
.toggle > span::after {
content: '';
@apply absolute w-4 h-4 rounded-sm bg-chalkboard-110;
top: 50%;
left: 50%;
translate: calc(-100% - var(--padding)) -50%;
transition: translate 0.08s ease-out;
}
.toggle input:checked + span::after {
translate: calc(50% - var(--padding)) -50%;
}

View File

@ -0,0 +1,34 @@
import styles from './Toggle.module.css'
interface ToggleProps {
className?: string
offLabel?: string
onLabel?: string
name: string
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
checked: boolean
}
export const Toggle = ({
className = '',
offLabel = 'Off',
onLabel = 'On',
name = '',
onChange,
checked,
}: ToggleProps) => {
return (
<label className={`${styles.toggle} ${className}`}>
{offLabel}
<input
type="checkbox"
name={name}
id={name}
checked={checked}
onChange={onChange}
/>
<span></span>
{onLabel}
</label>
)
}

View File

@ -1,4 +1,5 @@
@import '../node_modules/allotment/dist/style.css'; @import '../node_modules/allotment/dist/style.css';
@import './colors.css';
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;

View File

@ -2,16 +2,21 @@ import { faCheck, faFolder, faXmark } from '@fortawesome/free-solid-svg-icons'
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 { useStore } from '../useStore' import { baseUnits, useStore } from '../useStore'
import { useState } from 'react' import { useState } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { Toggle } from '../components/Toggle/Toggle'
export const Settings = () => { export const Settings = () => {
const { const {
defaultDir: originalDefaultDir, defaultDir: ogDefaultDir,
setDefaultDir: saveDefaultDir, setDefaultDir: saveDefaultDir,
defaultProjectName: originalDefaultProjectName, defaultProjectName: ogDefaultProjectName,
setDefaultProjectName: saveDefaultProjectName, setDefaultProjectName: saveDefaultProjectName,
defaultUnitSystem: ogDefaultUnitSystem,
setDefaultUnitSystem: saveDefaultUnitSystem,
defaultBaseUnit: ogDefaultBaseUnit,
setDefaultBaseUnit: saveDefaultBaseUnit,
saveDebugPanel, saveDebugPanel,
originalDebugPanel, originalDebugPanel,
} = useStore((s) => ({ } = useStore((s) => ({
@ -19,13 +24,19 @@ export const Settings = () => {
setDefaultDir: s.setDefaultDir, setDefaultDir: s.setDefaultDir,
defaultProjectName: s.defaultProjectName, defaultProjectName: s.defaultProjectName,
setDefaultProjectName: s.setDefaultProjectName, setDefaultProjectName: s.setDefaultProjectName,
defaultUnitSystem: s.defaultUnitSystem,
setDefaultUnitSystem: s.setDefaultUnitSystem,
defaultBaseUnit: s.defaultBaseUnit,
setDefaultBaseUnit: s.setDefaultBaseUnit,
saveDebugPanel: s.setDebugPanel, saveDebugPanel: s.setDebugPanel,
originalDebugPanel: s.debugPanel, originalDebugPanel: s.debugPanel,
})) }))
const [defaultDir, setDefaultDir] = useState(originalDefaultDir) const [defaultDir, setDefaultDir] = useState(ogDefaultDir)
const [defaultProjectName, setDefaultProjectName] = useState( const [defaultProjectName, setDefaultProjectName] =
originalDefaultProjectName useState(ogDefaultProjectName)
) const [defaultUnitSystem, setDefaultUnitSystem] =
useState(ogDefaultUnitSystem)
const [defaultBaseUnit, setDefaultBaseUnit] = useState(ogDefaultBaseUnit)
const [debugPanel, setDebugPanel] = useState(originalDebugPanel) const [debugPanel, setDebugPanel] = useState(originalDebugPanel)
async function handleDirectorySelection() { async function handleDirectorySelection() {
@ -43,6 +54,8 @@ export const Settings = () => {
const handleSaveClick = () => { const handleSaveClick = () => {
saveDefaultDir(defaultDir) saveDefaultDir(defaultDir)
saveDefaultProjectName(defaultProjectName) saveDefaultProjectName(defaultProjectName)
saveDefaultUnitSystem(defaultUnitSystem)
saveDefaultBaseUnit(defaultBaseUnit)
saveDebugPanel(debugPanel) saveDebugPanel(debugPanel)
toast.success('Settings saved!') toast.success('Settings saved!')
} }
@ -77,7 +90,7 @@ export const Settings = () => {
value={defaultDir.dir} value={defaultDir.dir}
onChange={(e) => onChange={(e) =>
setDefaultDir({ setDefaultDir({
base: originalDefaultDir.base, base: ogDefaultDir.base,
dir: e.target.value, dir: e.target.value,
}) })
} }
@ -109,12 +122,43 @@ export const Settings = () => {
onChange={(e) => setDefaultProjectName(e.target.value)} onChange={(e) => setDefaultProjectName(e.target.value)}
/> />
</SettingsSection> </SettingsSection>
<SettingsSection
title="Unit System"
description="Which unit system to use by default"
>
<Toggle
offLabel="Imperial"
onLabel="Metric"
name="settings-units"
checked={defaultUnitSystem === 'metric'}
onChange={(e) =>
setDefaultUnitSystem(e.target.checked ? 'metric' : 'imperial')
}
/>
</SettingsSection>
<SettingsSection
title="Base Unit"
description="Which base unit to use in dimensions by default"
>
<select
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)}
>
{baseUnits[defaultUnitSystem].map((unit) => (
<option key={unit} value={unit}>
{unit}
</option>
))}
</select>
</SettingsSection>
<SettingsSection <SettingsSection
title="Debug Panel" title="Debug Panel"
description="Show the debug panel in the editor" description="Show the debug panel in the editor"
> >
<input <Toggle
type="checkbox" name="settings-debug-panel"
checked={debugPanel} checked={debugPanel}
onChange={(e) => setDebugPanel(e.target.checked)} onChange={(e) => setDebugPanel(e.target.checked)}
/> />
@ -153,7 +197,7 @@ function SettingsSection({
<h2 className="text-2xl">{title}</h2> <h2 className="text-2xl">{title}</h2>
<p className="mt-2 text-sm">{description}</p> <p className="mt-2 text-sm">{description}</p>
</div> </div>
{children} <div>{children}</div>
</section> </section>
) )
} }

View File

@ -91,6 +91,13 @@ export type GuiModes =
position: Position position: Position
} }
type UnitSystem = 'imperial' | 'metric'
export const baseUnits: Record<UnitSystem, string[]> = {
imperial: ['in', 'ft'],
metric: ['mm', 'cm', 'm'],
}
interface DefaultDir { interface DefaultDir {
base?: BaseDirectory base?: BaseDirectory
dir: string dir: string
@ -152,6 +159,10 @@ export interface StoreState {
setDefaultDir: (dir: DefaultDir) => void setDefaultDir: (dir: DefaultDir) => void
defaultProjectName: string defaultProjectName: string
setDefaultProjectName: (defaultProjectName: string) => void setDefaultProjectName: (defaultProjectName: string) => void
defaultUnitSystem: UnitSystem,
setDefaultUnitSystem: (defaultUnitSystem: UnitSystem) => void
defaultBaseUnit: string,
setDefaultBaseUnit: (defaultBaseUnit: string) => void
showHomeMenu: boolean showHomeMenu: boolean
setHomeShowMenu: (showMenu: boolean) => void setHomeShowMenu: (showMenu: boolean) => void
homeMenuItems: { homeMenuItems: {
@ -314,6 +325,10 @@ export const useStore = create<StoreState>()(
setDefaultDir: (dir) => set({ defaultDir: dir }), setDefaultDir: (dir) => set({ defaultDir: dir }),
defaultProjectName: 'new-project-$n', defaultProjectName: 'new-project-$n',
setDefaultProjectName: (defaultProjectName) => set({ defaultProjectName }), setDefaultProjectName: (defaultProjectName) => set({ defaultProjectName }),
defaultUnitSystem: 'imperial',
setDefaultUnitSystem: (defaultUnitSystem) => set({ defaultUnitSystem }),
defaultBaseUnit: 'in',
setDefaultBaseUnit: (defaultBaseUnit) => set({ defaultBaseUnit }),
showHomeMenu: true, showHomeMenu: true,
setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }), setHomeShowMenu: (showHomeMenu) => set({ showHomeMenu }),
homeMenuItems: [], homeMenuItems: [],
@ -331,6 +346,8 @@ export const useStore = create<StoreState>()(
'code', 'code',
'defaultDir', 'defaultDir',
'defaultProjectName', 'defaultProjectName',
'defaultUnitSystem',
'defaultBaseUnit',
'token', 'token',
'debugPanel' 'debugPanel'
].includes(key)) ].includes(key))

View File

@ -2,7 +2,7 @@ import react from '@vitejs/plugin-react'
import viteTsconfigPaths from 'vite-tsconfig-paths' import viteTsconfigPaths from 'vite-tsconfig-paths'
import eslint from 'vite-plugin-eslint' import eslint from 'vite-plugin-eslint'
export default { const config = {
server: { server: {
open: true, open: true,
port: 3000, port: 3000,
@ -16,3 +16,5 @@ export default {
eslint(), eslint(),
], ],
} }
export default config

View File

@ -3519,6 +3519,14 @@ eslint-module-utils@^2.7.4:
dependencies: dependencies:
debug "^3.2.7" debug "^3.2.7"
eslint-plugin-css-modules@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-css-modules/-/eslint-plugin-css-modules-2.11.0.tgz#8de4d01d523a2d51c03043fa8004aab6b6cf3b1a"
integrity sha512-CLvQvJOMlCywZzaI4HVu7QH/ltgNXvCg7giJGiE+sA9wh5zQ+AqTgftAzrERV22wHe1p688wrU/Zwxt1Ry922w==
dependencies:
gonzales-pe "^4.0.3"
lodash "^4.17.2"
eslint-plugin-flowtype@^8.0.3: eslint-plugin-flowtype@^8.0.3:
version "8.0.3" version "8.0.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz#e1557e37118f24734aa3122e7536a038d34a4912" resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz#e1557e37118f24734aa3122e7536a038d34a4912"
@ -4095,6 +4103,13 @@ globrex@^0.1.2:
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
gonzales-pe@^4.0.3:
version "4.3.0"
resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3"
integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==
dependencies:
minimist "^1.2.5"
goober@^2.1.10: goober@^2.1.10:
version "2.1.13" version "2.1.13"
resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.13.tgz#e3c06d5578486212a76c9eba860cbc3232ff6d7c" resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.13.tgz#e3c06d5578486212a76c9eba860cbc3232ff6d7c"
@ -5181,7 +5196,7 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21: lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.21:
version "4.17.21" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -5295,6 +5310,11 @@ minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minimist@^1.2.5:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
minimist@^1.2.6: minimist@^1.2.6:
version "1.2.7" version "1.2.7"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"