@@ -1,41 +1,30 @@
|
||||
import { useRender } from 'src/components/IdeWrapper/useRender'
|
||||
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||
import { genParams } from 'src/helpers/cadPackages/jsCad/jscadParams'
|
||||
import { Switch } from '@headlessui/react'
|
||||
import Svg from 'src/components/Svg/Svg'
|
||||
import {
|
||||
CadhubStringParam,
|
||||
CadhubBooleanParam,
|
||||
CadhubNumberParam,
|
||||
} from './customizerConverter'
|
||||
|
||||
const Customizer = () => {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [shouldLiveUpdate, setShouldLiveUpdate] = React.useState(false)
|
||||
const ref = React.useRef()
|
||||
const jsCadCustomizerElement = ref.current
|
||||
const { state, thunkDispatch } = useIdeContext()
|
||||
const customizerParams = state?.customizerParams
|
||||
const currentParameters = state?.currentParameters
|
||||
const currentParameters = state?.currentParameters || {}
|
||||
const handleRender = useRender()
|
||||
|
||||
React.useEffect(() => {
|
||||
if (jsCadCustomizerElement && customizerParams) {
|
||||
genParams(
|
||||
customizerParams,
|
||||
jsCadCustomizerElement,
|
||||
currentParameters || {},
|
||||
(values, source) => {
|
||||
thunkDispatch({ type: 'setCurrentCustomizerParams', payload: values })
|
||||
if (shouldLiveUpdate) {
|
||||
handleRender()
|
||||
}
|
||||
},
|
||||
[]
|
||||
)
|
||||
const updateCustomizerParam = (paramName: string, paramValue: any) => {
|
||||
const payload = {
|
||||
...currentParameters,
|
||||
[paramName]: paramValue,
|
||||
}
|
||||
}, [
|
||||
jsCadCustomizerElement,
|
||||
customizerParams,
|
||||
currentParameters,
|
||||
shouldLiveUpdate,
|
||||
])
|
||||
if (!state.customizerParams) return null
|
||||
thunkDispatch({ type: 'setCurrentCustomizerParams', payload })
|
||||
shouldLiveUpdate && setTimeout(() => handleRender())
|
||||
}
|
||||
if (!customizerParams?.length) return null
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-x-0 bottom-0 bg-ch-gray-600 bg-opacity-60 text-ch-gray-300 text-lg font-fira-sans ${
|
||||
@@ -59,7 +48,6 @@ const Customizer = () => {
|
||||
<Switch
|
||||
checked={shouldLiveUpdate}
|
||||
onChange={(newValue) => {
|
||||
setShouldLiveUpdate
|
||||
if (newValue) handleRender()
|
||||
setShouldLiveUpdate(newValue)
|
||||
}}
|
||||
@@ -86,15 +74,184 @@ const Customizer = () => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${open ? 'h-full' : 'h-0'} overflow-y-auto px-12`}>
|
||||
<div
|
||||
id="jscad-customizer-block"
|
||||
ref={ref}
|
||||
// JSCAD param UI injected here.
|
||||
/>
|
||||
<div className={`${open ? 'h-full pb-32' : 'h-0'} overflow-y-auto px-12`}>
|
||||
<div>
|
||||
{customizerParams.map((param, index) => {
|
||||
const otherProps = {
|
||||
value: currentParameters[param.name],
|
||||
onChange: (value) => updateCustomizerParam(param.name, value),
|
||||
}
|
||||
if (param.type === 'string') {
|
||||
return <StringParam key={index} param={param} {...otherProps} />
|
||||
} else if (param.type === 'number') {
|
||||
return <NumberParam key={index} param={param} {...otherProps} />
|
||||
} else if (param.type === 'boolean') {
|
||||
return <BooleanParam key={index} param={param} {...otherProps} />
|
||||
}
|
||||
return <div key={index}>{JSON.stringify(param)}</div>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Customizer
|
||||
|
||||
function CustomizerParamBase({
|
||||
name,
|
||||
caption,
|
||||
children,
|
||||
}: {
|
||||
name: string
|
||||
caption: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<li
|
||||
className="grid items-center my-2"
|
||||
style={{ gridTemplateColumns: 'auto 8rem' }}
|
||||
>
|
||||
<div className=" text-sm font-fira-sans">
|
||||
<div className="font-bold text-base">{name}</div>
|
||||
<div>{caption}</div>
|
||||
</div>
|
||||
<div className="w-full">{children}</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function BooleanParam({
|
||||
param,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
param: CadhubBooleanParam
|
||||
value: any
|
||||
onChange: Function
|
||||
}) {
|
||||
return (
|
||||
<CustomizerParamBase name={param.name} caption={param.caption}>
|
||||
<Switch
|
||||
checked={value}
|
||||
onChange={(newValue) => {
|
||||
onChange(newValue)
|
||||
}}
|
||||
className={`${
|
||||
value ? 'bg-ch-gray-300' : 'bg-ch-gray-600'
|
||||
} relative inline-flex items-center h-6 rounded-full w-11 mr-6 border border-ch-gray-300`}
|
||||
>
|
||||
<span
|
||||
className={`${
|
||||
value ? 'translate-x-6' : 'translate-x-1'
|
||||
} inline-block w-4 h-4 transform bg-white rounded-full`}
|
||||
/>
|
||||
</Switch>
|
||||
</CustomizerParamBase>
|
||||
)
|
||||
}
|
||||
|
||||
function StringParam({
|
||||
param,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
param: CadhubStringParam
|
||||
value: any
|
||||
onChange: Function
|
||||
}) {
|
||||
return (
|
||||
<CustomizerParamBase name={param.name} caption={param.caption}>
|
||||
<input
|
||||
className="bg-transparent h-8 border border-ch-gray-300 px-2 text-sm w-full"
|
||||
type="text"
|
||||
value={value}
|
||||
placeholder={param.placeholder}
|
||||
onChange={({ target }) => onChange(target?.value)}
|
||||
/>
|
||||
</CustomizerParamBase>
|
||||
)
|
||||
}
|
||||
|
||||
function NumberParam({
|
||||
param,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
param: CadhubNumberParam
|
||||
value: any
|
||||
onChange: Function
|
||||
}) {
|
||||
const [isFocused, isFocusedSetter] = React.useState(false)
|
||||
const [localValue, localValueSetter] = React.useState(0)
|
||||
const [isLocked, isLockedSetter] = React.useState(false)
|
||||
const [pixelsDragged, pixelsDraggedSetter] = React.useState(0)
|
||||
const step = param.step || 1
|
||||
const commitChange = () => {
|
||||
let num = localValue
|
||||
if (typeof param.step === 'number') {
|
||||
num = Math.round(num / step) * step
|
||||
}
|
||||
if (typeof param.min === 'number') {
|
||||
num = Math.max(param.min, num)
|
||||
}
|
||||
if (typeof param.max === 'number') {
|
||||
num = Math.min(param.max, num)
|
||||
}
|
||||
num = Number(num.toFixed(2))
|
||||
localValueSetter(num)
|
||||
onChange(num)
|
||||
}
|
||||
React.useEffect(() => {
|
||||
if (!isFocused) commitChange()
|
||||
}, [isFocused])
|
||||
React.useEffect(() => localValueSetter(value), [value])
|
||||
return (
|
||||
<CustomizerParamBase name={param.name} caption={param.caption}>
|
||||
<div className="flex h-8 border border-ch-gray-300">
|
||||
<input
|
||||
className={`bg-transparent px-2 text-sm w-full ${
|
||||
(param.max && param.max < localValue) ||
|
||||
(param.min && param.min > localValue)
|
||||
? 'text-red-500'
|
||||
: ''
|
||||
}`}
|
||||
type="number"
|
||||
value={localValue}
|
||||
onFocus={() => isFocusedSetter(true)}
|
||||
onBlur={() => isFocusedSetter(false)}
|
||||
onKeyDown={({ key }) => key === 'Enter' && commitChange()}
|
||||
onChange={({ target }) => {
|
||||
const num = Number(target?.value)
|
||||
localValueSetter(num)
|
||||
}}
|
||||
max={param.max}
|
||||
min={param.min}
|
||||
step={step}
|
||||
/>
|
||||
<div
|
||||
className="w-6 border-l border-ch-gray-500 items-center hidden md:flex"
|
||||
style={{ cursor: 'ew-resize' }}
|
||||
onMouseDown={({ target }) => {
|
||||
isLockedSetter(true)
|
||||
target?.requestPointerLock?.()
|
||||
pixelsDraggedSetter(localValue)
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
isLockedSetter(false)
|
||||
document?.exitPointerLock?.()
|
||||
commitChange()
|
||||
}}
|
||||
onMouseMove={({ movementX }) => {
|
||||
if (isLocked && movementX) {
|
||||
pixelsDraggedSetter(pixelsDragged + (movementX * step) / 8) // one step per 8 pixels
|
||||
localValueSetter(Number(pixelsDragged.toFixed(2)))
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Svg className="w-6" name="switch-horizontal" />
|
||||
</div>
|
||||
</div>
|
||||
</CustomizerParamBase>
|
||||
)
|
||||
}
|
||||
|
||||
120
app/web/src/components/Customizer/customizerConverter.ts
Normal file
120
app/web/src/components/Customizer/customizerConverter.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
// CadHub
|
||||
|
||||
type CadhubTypeNames = 'number' | 'string' | 'boolean'
|
||||
|
||||
interface CadhubParamBase {
|
||||
type: CadhubTypeNames
|
||||
caption: string
|
||||
name: string
|
||||
}
|
||||
export interface CadhubStringParam extends CadhubParamBase {
|
||||
type: 'string'
|
||||
initial: string
|
||||
placeholder?: string
|
||||
maxLength?: number
|
||||
}
|
||||
export interface CadhubBooleanParam extends CadhubParamBase {
|
||||
type: 'boolean'
|
||||
initial?: boolean
|
||||
}
|
||||
export interface CadhubNumberParam extends CadhubParamBase {
|
||||
type: 'number'
|
||||
initial: number
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
decimal?: number
|
||||
}
|
||||
|
||||
export type CadhubParams =
|
||||
| CadhubStringParam
|
||||
| CadhubBooleanParam
|
||||
| CadhubNumberParam
|
||||
|
||||
// OpenSCAD
|
||||
const openscadValues = `
|
||||
// slider widget for number with max. value
|
||||
sliderWithMax =34; // [50]
|
||||
|
||||
// slider widget for number in range
|
||||
sliderWithRange =34; // [10:100]
|
||||
|
||||
//step slider for number
|
||||
stepSlider=2; //[0:5:100]
|
||||
|
||||
// slider widget for number in range
|
||||
sliderCentered =0; // [-10:0.1:10]
|
||||
|
||||
// spinbox with step size 1
|
||||
Spinbox= 5;
|
||||
|
||||
// Text box for string
|
||||
String="hello";
|
||||
|
||||
// Text box for string with length 8
|
||||
String2="length"; //8
|
||||
|
||||
//description
|
||||
Variable = true;
|
||||
`
|
||||
|
||||
const openscadConverted: CadhubParams[] = [
|
||||
{
|
||||
type: 'number',
|
||||
name: 'sliderWithMax',
|
||||
caption: 'slider widget for number with max. value',
|
||||
initial: 34,
|
||||
step: 1,
|
||||
max: 50,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'sliderWithRange',
|
||||
caption: 'slider widget for number in range',
|
||||
initial: 34,
|
||||
step: 1,
|
||||
min: 10,
|
||||
max: 100,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'stepSlider',
|
||||
caption: 'step slider for number',
|
||||
initial: 2,
|
||||
step: 5,
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'sliderCentered',
|
||||
caption: 'slider widget for number in range',
|
||||
initial: 0,
|
||||
step: 0.1,
|
||||
min: -10,
|
||||
max: 10,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'Spinbox',
|
||||
caption: 'spinbox with step size 1',
|
||||
initial: 5,
|
||||
step: 1,
|
||||
},
|
||||
|
||||
{
|
||||
type: 'string',
|
||||
name: 'String',
|
||||
caption: 'Text box for string',
|
||||
initial: 'hello',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'String2',
|
||||
caption: 'Text box for string with length 8',
|
||||
initial: 'length',
|
||||
maxLength: 8,
|
||||
},
|
||||
|
||||
{ type: 'boolean', name: 'Variable', caption: 'description', initial: true },
|
||||
]
|
||||
Reference in New Issue
Block a user