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:
@ -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": [
|
||||||
|
@ -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",
|
||||||
|
32
src/components/Toggle/Toggle.module.css
Normal file
32
src/components/Toggle/Toggle.module.css
Normal 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%;
|
||||||
|
}
|
34
src/components/Toggle/Toggle.tsx
Normal file
34
src/components/Toggle/Toggle.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
22
yarn.lock
22
yarn.lock
@ -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"
|
||||||
|
Reference in New Issue
Block a user