diff --git a/.vscode/settings.json b/.vscode/settings.json
index e9d5f61..aaa161b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,6 @@
{
"cSpell.words": [
+ "Cadhub",
"Customizer",
"Hutten",
"cadquery",
diff --git a/app/web/src/components/Customizer/Customizer.tsx b/app/web/src/components/Customizer/Customizer.tsx
index ced655e..53f170e 100644
--- a/app/web/src/components/Customizer/Customizer.tsx
+++ b/app/web/src/components/Customizer/Customizer.tsx
@@ -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 (
{
{
- setShouldLiveUpdate
if (newValue) handleRender()
setShouldLiveUpdate(newValue)
}}
@@ -86,15 +74,184 @@ const Customizer = () => {
>
)}
-
-
+
+
+ {customizerParams.map((param, index) => {
+ const otherProps = {
+ value: currentParameters[param.name],
+ onChange: (value) => updateCustomizerParam(param.name, value),
+ }
+ if (param.type === 'string') {
+ return
+ } else if (param.type === 'number') {
+ return
+ } else if (param.type === 'boolean') {
+ return
+ }
+ return
{JSON.stringify(param)}
+ })}
+
)
}
export default Customizer
+
+function CustomizerParamBase({
+ name,
+ caption,
+ children,
+}: {
+ name: string
+ caption: string
+ children: React.ReactNode
+}) {
+ return (
+
+
+ {children}
+
+ )
+}
+
+function BooleanParam({
+ param,
+ value,
+ onChange,
+}: {
+ param: CadhubBooleanParam
+ value: any
+ onChange: Function
+}) {
+ return (
+
+ {
+ 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`}
+ >
+
+
+
+ )
+}
+
+function StringParam({
+ param,
+ value,
+ onChange,
+}: {
+ param: CadhubStringParam
+ value: any
+ onChange: Function
+}) {
+ return (
+
+ onChange(target?.value)}
+ />
+
+ )
+}
+
+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 (
+
+
+
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}
+ />
+
{
+ 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)))
+ }
+ }}
+ >
+
+
+
+
+ )
+}
diff --git a/app/web/src/components/Customizer/customizerConverter.ts b/app/web/src/components/Customizer/customizerConverter.ts
new file mode 100644
index 0000000..fee6128
--- /dev/null
+++ b/app/web/src/components/Customizer/customizerConverter.ts
@@ -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 },
+]
diff --git a/app/web/src/components/Svg/Svg.tsx b/app/web/src/components/Svg/Svg.tsx
index f21612d..ee46676 100644
--- a/app/web/src/components/Svg/Svg.tsx
+++ b/app/web/src/components/Svg/Svg.tsx
@@ -27,6 +27,7 @@ type SvgNames =
| 'refresh'
| 'save'
| 'share'
+ | 'switch-horizontal'
| 'terminal'
| 'trash'
| 'x'
@@ -503,6 +504,21 @@ const Svg = ({
/>
),
+ 'switch-horizontal': (
+
+ ),
terminal: (