CadHub Customizer #461
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"Cadhub",
|
||||||
"Customizer",
|
"Customizer",
|
||||||
"Hutten",
|
"Hutten",
|
||||||
"cadquery",
|
"cadquery",
|
||||||
|
|||||||
@@ -1,41 +1,30 @@
|
|||||||
import { useRender } from 'src/components/IdeWrapper/useRender'
|
import { useRender } from 'src/components/IdeWrapper/useRender'
|
||||||
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
import { useIdeContext } from 'src/helpers/hooks/useIdeContext'
|
||||||
import { genParams } from 'src/helpers/cadPackages/jsCad/jscadParams'
|
|
||||||
import { Switch } from '@headlessui/react'
|
import { Switch } from '@headlessui/react'
|
||||||
import Svg from 'src/components/Svg/Svg'
|
import Svg from 'src/components/Svg/Svg'
|
||||||
|
import {
|
||||||
|
CadhubStringParam,
|
||||||
|
CadhubBooleanParam,
|
||||||
|
CadhubNumberParam,
|
||||||
|
} from './customizerConverter'
|
||||||
|
|
||||||
const Customizer = () => {
|
const Customizer = () => {
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = React.useState(false)
|
||||||
const [shouldLiveUpdate, setShouldLiveUpdate] = React.useState(false)
|
const [shouldLiveUpdate, setShouldLiveUpdate] = React.useState(false)
|
||||||
const ref = React.useRef()
|
|
||||||
const jsCadCustomizerElement = ref.current
|
|
||||||
const { state, thunkDispatch } = useIdeContext()
|
const { state, thunkDispatch } = useIdeContext()
|
||||||
const customizerParams = state?.customizerParams
|
const customizerParams = state?.customizerParams
|
||||||
const currentParameters = state?.currentParameters
|
const currentParameters = state?.currentParameters || {}
|
||||||
const handleRender = useRender()
|
const handleRender = useRender()
|
||||||
|
|
||||||
React.useEffect(() => {
|
const updateCustomizerParam = (paramName: string, paramValue: any) => {
|
||||||
if (jsCadCustomizerElement && customizerParams) {
|
const payload = {
|
||||||
genParams(
|
...currentParameters,
|
||||||
customizerParams,
|
[paramName]: paramValue,
|
||||||
jsCadCustomizerElement,
|
|
||||||
currentParameters || {},
|
|
||||||
(values, source) => {
|
|
||||||
thunkDispatch({ type: 'setCurrentCustomizerParams', payload: values })
|
|
||||||
if (shouldLiveUpdate) {
|
|
||||||
handleRender()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}, [
|
thunkDispatch({ type: 'setCurrentCustomizerParams', payload })
|
||||||
jsCadCustomizerElement,
|
shouldLiveUpdate && setTimeout(() => handleRender())
|
||||||
customizerParams,
|
}
|
||||||
currentParameters,
|
if (!customizerParams?.length) return null
|
||||||
shouldLiveUpdate,
|
|
||||||
])
|
|
||||||
if (!state.customizerParams) return null
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`absolute inset-x-0 bottom-0 bg-ch-gray-600 bg-opacity-60 text-ch-gray-300 text-lg font-fira-sans ${
|
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
|
<Switch
|
||||||
checked={shouldLiveUpdate}
|
checked={shouldLiveUpdate}
|
||||||
onChange={(newValue) => {
|
onChange={(newValue) => {
|
||||||
setShouldLiveUpdate
|
|
||||||
if (newValue) handleRender()
|
if (newValue) handleRender()
|
||||||
setShouldLiveUpdate(newValue)
|
setShouldLiveUpdate(newValue)
|
||||||
}}
|
}}
|
||||||
@@ -86,15 +74,184 @@ const Customizer = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={`${open ? 'h-full' : 'h-0'} overflow-y-auto px-12`}>
|
<div className={`${open ? 'h-full pb-32' : 'h-0'} overflow-y-auto px-12`}>
|
||||||
<div
|
<div>
|
||||||
id="jscad-customizer-block"
|
{customizerParams.map((param, index) => {
|
||||||
ref={ref}
|
const otherProps = {
|
||||||
// JSCAD param UI injected here.
|
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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Customizer
|
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 },
|
||||||
|
]
|
||||||
@@ -27,6 +27,7 @@ type SvgNames =
|
|||||||
| 'refresh'
|
| 'refresh'
|
||||||
| 'save'
|
| 'save'
|
||||||
| 'share'
|
| 'share'
|
||||||
|
| 'switch-horizontal'
|
||||||
| 'terminal'
|
| 'terminal'
|
||||||
| 'trash'
|
| 'trash'
|
||||||
| 'x'
|
| 'x'
|
||||||
@@ -503,6 +504,21 @@ const Svg = ({
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
|
'switch-horizontal': (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
terminal: (
|
terminal: (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
||||||
import { State } from 'src/helpers/hooks/useIdeState'
|
import { State } from 'src/helpers/hooks/useIdeState'
|
||||||
|
import { CadhubParams } from 'src/components/Customizer/customizerConverter'
|
||||||
|
|
||||||
export const lambdaBaseURL =
|
export const lambdaBaseURL =
|
||||||
process.env.CAD_LAMBDA_BASE_URL ||
|
process.env.CAD_LAMBDA_BASE_URL ||
|
||||||
@@ -45,14 +46,12 @@ export function createHealthyResponse({
|
|||||||
consoleMessage,
|
consoleMessage,
|
||||||
type,
|
type,
|
||||||
customizerParams,
|
customizerParams,
|
||||||
currentParameters,
|
|
||||||
}: {
|
}: {
|
||||||
date: Date
|
date: Date
|
||||||
data: any
|
data: any
|
||||||
consoleMessage: string
|
consoleMessage: string
|
||||||
type: HealthyResponse['objectData']['type']
|
type: HealthyResponse['objectData']['type']
|
||||||
customizerParams?: any
|
customizerParams?: CadhubParams[]
|
||||||
currentParameters?: any
|
|
||||||
}): HealthyResponse {
|
}): HealthyResponse {
|
||||||
return {
|
return {
|
||||||
status: 'healthy',
|
status: 'healthy',
|
||||||
@@ -66,7 +65,6 @@ export function createHealthyResponse({
|
|||||||
time: date,
|
time: date,
|
||||||
},
|
},
|
||||||
customizerParams,
|
customizerParams,
|
||||||
currentParameters,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +97,7 @@ export function createUnhealthyResponse(
|
|||||||
|
|
||||||
export const timeoutErrorMessage = `timeout: We're currently limited to a 30s execution time. You can try again, sometimes it works the second time`
|
export const timeoutErrorMessage = `timeout: We're currently limited to a 30s execution time. You can try again, sometimes it works the second time`
|
||||||
|
|
||||||
|
export type RenderResponse = HealthyResponse | ErrorResponse
|
||||||
export interface DefaultKernelExport {
|
export interface DefaultKernelExport {
|
||||||
render: (arg: RenderArgs) => Promise<HealthyResponse | ErrorResponse>
|
render: (arg: RenderArgs) => Promise<RenderResponse>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
RenderArgs,
|
RenderArgs,
|
||||||
DefaultKernelExport,
|
DefaultKernelExport,
|
||||||
|
RenderResponse,
|
||||||
createUnhealthyResponse,
|
createUnhealthyResponse,
|
||||||
createHealthyResponse,
|
createHealthyResponse,
|
||||||
} from '../common'
|
} from '../common'
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
Color,
|
Color,
|
||||||
Mesh,
|
Mesh,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
|
import { jsCadToCadhubParams } from './jscadParams'
|
||||||
|
|
||||||
const materials = {
|
const materials = {
|
||||||
mesh: {
|
mesh: {
|
||||||
@@ -70,15 +72,46 @@ function CSG2Object3D(obj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let scriptWorker
|
let scriptWorker
|
||||||
let currentParameters = {}
|
|
||||||
const scriptUrl = '/demo-worker.js'
|
|
||||||
let resolveReference = null
|
|
||||||
let response = null
|
|
||||||
|
|
||||||
const callResolve = () => {
|
type ResolveFn = (RenderResponse) => void
|
||||||
if (resolveReference) resolveReference()
|
|
||||||
resolveReference
|
class WorkerHelper {
|
||||||
|
callResolve: null | ResolveFn = null
|
||||||
|
previousCode = ''
|
||||||
|
resolver = (response: RenderResponse) => {
|
||||||
|
this.callResolve && this.callResolve(response)
|
||||||
|
this.callResolve = null
|
||||||
|
}
|
||||||
|
render = (
|
||||||
|
code: string,
|
||||||
|
parameters: { [key: string]: any }
|
||||||
|
): Promise<RenderResponse> => {
|
||||||
|
const response: Promise<RenderResponse> = new Promise(
|
||||||
|
(resolve: ResolveFn) => {
|
||||||
|
this.callResolve = resolve
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!(code && this.previousCode !== code)) {
|
||||||
|
// we are not evaluating code, but reacting to parameters change
|
||||||
|
scriptWorker.postMessage({
|
||||||
|
action: 'updateParams',
|
||||||
|
worker: 'script',
|
||||||
|
params: parameters,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
scriptWorker.postMessage({
|
||||||
|
action: 'runScript',
|
||||||
|
worker: 'script',
|
||||||
|
script: code,
|
||||||
|
params: parameters || {},
|
||||||
|
url: 'jscad_script',
|
||||||
|
})
|
||||||
|
fundamentally i haven't changed how your code works, I just wanted to group things a little better in this file as I found some of the module level variables hard to keep track of and so instead moved some of the stuff that required state into this class. Plus one other thing that made me uncomfortable was how the function awaited a promise just to return another module level variable This render function now sets up promise -> posts a message to the worker -> then returns the promise where the promise now contains the final response. I at least personally find it easier to reason about. fundamentally i haven't changed how your code works, I just wanted to group things a little better in this file as I found some of the module level variables hard to keep track of and so instead moved some of the stuff that required state into this class. Plus one other thing that made me uncomfortable was how the function awaited a promise just to return another module level variable `response`.
This render function now sets up promise -> posts a message to the worker -> then returns the promise where the promise now contains the final response. I at least personally find it easier to reason about.
yes, it looks cleaner. thnx :) yes, it looks cleaner. thnx :)
|
|||||||
|
}
|
||||||
|
this.previousCode = code
|
||||||
|
return response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
const workerHelper = new WorkerHelper()
|
||||||
|
|
||||||
export const render: DefaultKernelExport['render'] = async ({
|
export const render: DefaultKernelExport['render'] = async ({
|
||||||
code,
|
code,
|
||||||
@@ -86,10 +119,8 @@ export const render: DefaultKernelExport['render'] = async ({
|
|||||||
settings,
|
settings,
|
||||||
}: RenderArgs) => {
|
}: RenderArgs) => {
|
||||||
if (!scriptWorker) {
|
if (!scriptWorker) {
|
||||||
console.trace(
|
|
||||||
'************************** creating new worker ************************'
|
|
||||||
)
|
|
||||||
const baseURI = document.baseURI.toString()
|
const baseURI = document.baseURI.toString()
|
||||||
|
const scriptUrl = '/demo-worker.js'
|
||||||
const script = `let baseURI = '${baseURI}'
|
const script = `let baseURI = '${baseURI}'
|
||||||
importScripts(new URL('${scriptUrl}',baseURI))
|
importScripts(new URL('${scriptUrl}',baseURI))
|
||||||
let worker = jscadWorker({
|
let worker = jscadWorker({
|
||||||
@@ -103,65 +134,31 @@ export const render: DefaultKernelExport['render'] = async ({
|
|||||||
const blob = new Blob([script], { type: 'text/javascript' })
|
const blob = new Blob([script], { type: 'text/javascript' })
|
||||||
scriptWorker = new Worker(window.URL.createObjectURL(blob))
|
scriptWorker = new Worker(window.URL.createObjectURL(blob))
|
||||||
let parameterDefinitions = []
|
let parameterDefinitions = []
|
||||||
scriptWorker.addEventListener('message', (e) => {
|
scriptWorker.addEventListener('message', ({ data }) => {
|
||||||
const data = e.data
|
|
||||||
if (data.action == 'parameterDefinitions') {
|
if (data.action == 'parameterDefinitions') {
|
||||||
parameterDefinitions = data.data
|
parameterDefinitions = data.data
|
||||||
} else if (data.action == 'entities') {
|
} else if (data.action == 'entities') {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
response = createUnhealthyResponse(new Date(), data.error)
|
workerHelper.resolver(createUnhealthyResponse(new Date(), data.error))
|
||||||
} else {
|
} else {
|
||||||
response = createHealthyResponse({
|
workerHelper.resolver(
|
||||||
type: 'geometry',
|
createHealthyResponse({
|
||||||
data: [...data.entities.map(CSG2Object3D).filter((o) => o)],
|
type: 'geometry',
|
||||||
consoleMessage: data.scriptStats,
|
data: data.entities.map(CSG2Object3D).filter((o) => o),
|
||||||
date: new Date(),
|
consoleMessage: data.scriptStats,
|
||||||
customizerParams: parameterDefinitions,
|
date: new Date(),
|
||||||
currentParameters,
|
customizerParams: jsCadToCadhubParams(parameterDefinitions || []),
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
callResolve()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
callResolve()
|
workerHelper.resolver()
|
||||||
response = null
|
|
||||||
scriptWorker.postMessage({ action: 'init', baseURI, alias: [] })
|
scriptWorker.postMessage({ action: 'init', baseURI, alias: [] })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
return workerHelper.render(code, parameters)
|
||||||
parameters &&
|
|
||||||
currentParameters &&
|
|
||||||
JSON.stringify(parameters) !== JSON.stringify(currentParameters)
|
|
||||||
) {
|
|
||||||
// we are not evaluating code, but reacting to parameters change
|
|
||||||
scriptWorker.postMessage({
|
|
||||||
action: 'updateParams',
|
|
||||||
worker: 'script',
|
|
||||||
params: parameters,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
scriptWorker.postMessage({
|
|
||||||
action: 'runScript',
|
|
||||||
worker: 'script',
|
|
||||||
script: code,
|
|
||||||
params: parameters || {},
|
|
||||||
url: 'jscad_script',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// we need this to keep the form filled with same data when new parameter definitions arrive
|
|
||||||
// each render of the script could provide new paramaters. In case some of them are still rpesent
|
|
||||||
// it is expected for them to stay the same and not just reset
|
|
||||||
currentParameters = parameters || {}
|
|
||||||
|
|
||||||
const waitResult = new Promise((resolve) => {
|
|
||||||
resolveReference = resolve
|
|
||||||
})
|
|
||||||
|
|
||||||
await waitResult
|
|
||||||
resolveReference = null
|
|
||||||
if (parameters) delete response.customizerParams
|
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsCadController: DefaultKernelExport = {
|
const jsCadController: DefaultKernelExport = {
|
||||||
|
|||||||
@@ -1,216 +1,130 @@
|
|||||||
import type { RawCustomizerParams } from '../common'
|
import { CadhubParams } from 'src/components/Customizer/customizerConverter'
|
||||||
|
|
||||||
const GROUP_SELECTOR = 'DIV[type="group"]'
|
type JscadTypeNames =
|
||||||
const INPUT_SELECTOR = 'INPUT, SELECT'
|
| 'group'
|
||||||
|
| 'text'
|
||||||
|
| 'int'
|
||||||
|
| 'number'
|
||||||
|
| 'slider'
|
||||||
|
| 'email'
|
||||||
|
| 'password'
|
||||||
|
| 'date'
|
||||||
|
| 'url'
|
||||||
|
| 'checkbox'
|
||||||
|
| 'color'
|
||||||
|
| 'choice'
|
||||||
|
| 'radio'
|
||||||
|
|
||||||
function forEachInput(
|
interface JscadParamBase {
|
||||||
target: HTMLElement,
|
type: JscadTypeNames
|
||||||
callback: (e: HTMLInputElement) => void
|
caption: string
|
||||||
) {
|
name: string
|
||||||
target.querySelectorAll(INPUT_SELECTOR).forEach(callback)
|
}
|
||||||
|
interface JscadGroupParam extends JscadParamBase {
|
||||||
|
type: 'group'
|
||||||
|
initial?: 'open' | 'closed'
|
||||||
|
}
|
||||||
|
interface JscadTextParam extends JscadParamBase {
|
||||||
|
type: 'text'
|
||||||
|
initial: string
|
||||||
|
placeholder: string
|
||||||
|
size: number
|
||||||
|
maxLength: number
|
||||||
|
}
|
||||||
|
interface JscadIntNumberSliderParam extends JscadParamBase {
|
||||||
|
type: 'int' | 'number' | 'slider'
|
||||||
|
initial: number
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
step?: number
|
||||||
|
}
|
||||||
|
interface JscadDateParam extends JscadParamBase {
|
||||||
|
type: 'date'
|
||||||
|
initial: string
|
||||||
|
min: string
|
||||||
|
max: string
|
||||||
|
placeholder: string
|
||||||
|
}
|
||||||
|
interface JscadEmailPasswordColorParam extends JscadParamBase {
|
||||||
|
type: 'email' | 'password' | 'color'
|
||||||
|
initial: string
|
||||||
|
}
|
||||||
|
interface JscadUrlParam extends JscadParamBase {
|
||||||
|
type: 'url'
|
||||||
|
initial: string
|
||||||
|
maxLength: number
|
||||||
|
size: number
|
||||||
|
placeholder: string
|
||||||
|
}
|
||||||
|
interface JscadCheckboxParam extends JscadParamBase {
|
||||||
|
type: 'checkbox'
|
||||||
|
checked: boolean
|
||||||
|
initial: boolean
|
||||||
|
}
|
||||||
|
interface JscadChoiceRadioParam extends JscadParamBase {
|
||||||
|
type: 'choice' | 'radio'
|
||||||
|
initial: number | string
|
||||||
|
values: (string | number)[]
|
||||||
|
captions?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
function forEachGroup(target: HTMLElement, callback: (e: HTMLElement) => void) {
|
type JsCadParams =
|
||||||
target.querySelectorAll(GROUP_SELECTOR).forEach(callback)
|
| JscadGroupParam
|
||||||
}
|
| JscadTextParam
|
||||||
|
| JscadIntNumberSliderParam
|
||||||
|
| JscadDateParam
|
||||||
|
| JscadEmailPasswordColorParam
|
||||||
|
| JscadUrlParam
|
||||||
|
| JscadCheckboxParam
|
||||||
|
| JscadChoiceRadioParam
|
||||||
|
|
||||||
const numeric = { number: 1, float: 1, int: 1, range: 1, slider: 1 }
|
export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] {
|
||||||
|
return input
|
||||||
function applyRange(inp) {
|
.map((param): CadhubParams => {
|
||||||
const label = inp.previousElementSibling
|
switch (param.type) {
|
||||||
if (label && label.tagName == 'LABEL') {
|
case 'slider':
|
||||||
const info = label.querySelector('I')
|
case 'number':
|
||||||
if (info) info.innerHTML = inp.value
|
case 'int':
|
||||||
}
|
return {
|
||||||
}
|
type: 'number',
|
||||||
|
caption: param.caption,
|
||||||
export function genParams(
|
name: param.name,
|
||||||
defs,
|
initial: param.initial,
|
||||||
target,
|
min: param.min,
|
||||||
storedParams = {},
|
max: param.max,
|
||||||
callback: (values: RawCustomizerParams, source: any) => void = undefined,
|
step: param.step,
|
||||||
buttons = ['reset', 'save', 'load', 'edit', 'link']
|
decimal: param.step % 1 === 0 && param.initial % 1 === 0 ? 0 : 2,
|
||||||
) {
|
}
|
||||||
const funcs = {
|
case 'text':
|
||||||
group: () => '',
|
case 'url':
|
||||||
choice: inputChoice,
|
case 'email':
|
||||||
radio: inputRadio,
|
case 'password':
|
||||||
float: inputNumber,
|
case 'color':
|
||||||
range: inputNumber,
|
case 'date':
|
||||||
slider: inputNumber,
|
return {
|
||||||
int: inputNumber,
|
type: 'string',
|
||||||
text: inputNumber,
|
caption: param.caption,
|
||||||
url: inputNumber,
|
name: param.name,
|
||||||
email: inputNumber,
|
initial: param.initial,
|
||||||
date: inputNumber,
|
placeholder:
|
||||||
password: inputNumber,
|
param.type === 'text' ||
|
||||||
color: inputNumber,
|
param.type === 'date' ||
|
||||||
// TODO radio similar options as choice
|
param.type === 'url'
|
||||||
checkbox: function ({ name, value }) {
|
? param.placeholder
|
||||||
const checkedStr = value === 'checked' || value === true ? 'checked' : ''
|
: '',
|
||||||
return `<input type="checkbox" name="${name}" ${checkedStr}/>`
|
maxLength:
|
||||||
},
|
param.type === 'text' || param.type === 'url'
|
||||||
number: inputNumber,
|
? param.maxLength
|
||||||
}
|
: undefined,
|
||||||
|
}
|
||||||
function inputRadio({ name, type, captions, value, values }) {
|
case 'checkbox':
|
||||||
if (!captions) captions = values
|
return {
|
||||||
|
type: 'boolean',
|
||||||
let ret = '<div type="radio">'
|
caption: param.caption,
|
||||||
|
name: param.name,
|
||||||
for (let i = 0; i < values.length; i++) {
|
initial: !!param.initial,
|
||||||
const checked =
|
}
|
||||||
value == values[i] || value == captions[i] ? 'checked' : ''
|
}
|
||||||
ret += `<label><input type="radio" _type="${type}" name="${name}" numeric="${
|
|
||||||
typeof values[0] == 'number' ? '1' : '0'
|
|
||||||
}" value="${values[i]}" ${checked}/>${captions[i]}</label>`
|
|
||||||
}
|
|
||||||
return ret + '</div>'
|
|
||||||
}
|
|
||||||
|
|
||||||
function inputChoice({ name, type, captions, value, values }) {
|
|
||||||
if (!captions) captions = values
|
|
||||||
|
|
||||||
let ret = `<select _type="${type}" name="${name}" numeric="${
|
|
||||||
typeof values[0] == 'number' ? '1' : '0'
|
|
||||||
}">`
|
|
||||||
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
const checked =
|
|
||||||
value == values[i] || value == captions[i] ? 'selected' : ''
|
|
||||||
ret += `<option value="${values[i]}" ${checked}>${captions[i]}</option>`
|
|
||||||
}
|
|
||||||
return ret + '</select>'
|
|
||||||
}
|
|
||||||
|
|
||||||
function inputNumber(def) {
|
|
||||||
let { name, type, value, min, max, step, placeholder, live } = def
|
|
||||||
if (value === null || value === undefined) value = numeric[type] ? 0 : ''
|
|
||||||
let inputType = type
|
|
||||||
if (type == 'int' || type == 'float') inputType = 'number'
|
|
||||||
if (type == 'range' || type == 'slider') inputType = 'range'
|
|
||||||
let str = `<input _type="${type}" type="${inputType}" name="${name}"`
|
|
||||||
if (step !== undefined) str += ` step="${step || ''}"`
|
|
||||||
if (min !== undefined) str += ` min="${min || ''}"`
|
|
||||||
if (max !== undefined) str += ` max="${max || ''}"`
|
|
||||||
if (value !== undefined) str += ` value="${value}"`
|
|
||||||
str += ` live="${live ? 1 : 0}"`
|
|
||||||
if (placeholder !== undefined) str += ` placeholder="${placeholder}"`
|
|
||||||
return str + '/>'
|
|
||||||
}
|
|
||||||
|
|
||||||
let html = ''
|
|
||||||
let closed = false
|
|
||||||
const missing = {}
|
|
||||||
|
|
||||||
defs.forEach((def) => {
|
|
||||||
const { type, caption, name } = def
|
|
||||||
|
|
||||||
if (storedParams[name] !== undefined) {
|
|
||||||
def.value = storedParams[name]
|
|
||||||
} else {
|
|
||||||
def.value = def.initial || def['default'] || def.checked
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == 'group') {
|
|
||||||
closed = def.value == 'closed'
|
|
||||||
}
|
|
||||||
def.closed = closed
|
|
||||||
|
|
||||||
html += `<div class="form-line" type="${def.type}" closed="${
|
|
||||||
closed ? 1 : 0
|
|
||||||
}" `
|
|
||||||
if (type == 'group') html += ` name="${name}"`
|
|
||||||
html += `">`
|
|
||||||
|
|
||||||
html += `<label`
|
|
||||||
if (type == 'group') html += ` name="${name}"`
|
|
||||||
html += `>`
|
|
||||||
if (type == 'checkbox') html += funcs[type](def)
|
|
||||||
html += `${caption}<i>${def.value}</i></label>`
|
|
||||||
|
|
||||||
if (funcs[type] && type != 'checkbox') html += funcs[type](def)
|
|
||||||
|
|
||||||
if (!funcs[type]) missing[type] = 1
|
|
||||||
|
|
||||||
html += '</div>\n'
|
|
||||||
})
|
|
||||||
|
|
||||||
const missingKeys = Object.keys(missing)
|
|
||||||
if (missingKeys.length) console.log('missing param impl', missingKeys)
|
|
||||||
|
|
||||||
function _callback(source = 'change') {
|
|
||||||
if (callback && source !== 'group') callback(getParams(target), source)
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '<div class="jscad-param-buttons"><div>'
|
|
||||||
buttons.forEach((button) => {
|
|
||||||
const { id, name } =
|
|
||||||
typeof button === 'string' ? { id: button, name: button } : button
|
|
||||||
html += `<button action="${id}"><b>${name}</b></button>`
|
|
||||||
})
|
|
||||||
html += '</div></div>'
|
|
||||||
|
|
||||||
target.innerHTML = html
|
|
||||||
|
|
||||||
forEachInput(target, (inp) => {
|
|
||||||
const type = inp.type
|
|
||||||
inp.addEventListener('input', function (evt) {
|
|
||||||
applyRange(inp)
|
|
||||||
if (inp.getAttribute('live') === '1') _callback('live')
|
|
||||||
})
|
})
|
||||||
if (inp.getAttribute('live') !== '1')
|
.filter((a) => a)
|
||||||
inp.addEventListener('change', () => _callback('change'))
|
|
||||||
})
|
|
||||||
|
|
||||||
function groupClick(evt) {
|
|
||||||
let groupDiv = evt.target
|
|
||||||
if (groupDiv.tagName === 'LABEL') groupDiv = groupDiv.parentNode
|
|
||||||
const closed = groupDiv.getAttribute('closed') == '1' ? '0' : '1'
|
|
||||||
do {
|
|
||||||
groupDiv.setAttribute('closed', closed)
|
|
||||||
groupDiv = groupDiv.nextElementSibling
|
|
||||||
} while (groupDiv && groupDiv.getAttribute('type') != 'group')
|
|
||||||
_callback('group')
|
|
||||||
}
|
|
||||||
|
|
||||||
forEachGroup(target, (div) => {
|
|
||||||
div.onclick = groupClick
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getParams(target: HTMLElement): RawCustomizerParams {
|
|
||||||
const params = {}
|
|
||||||
if (!target) return params
|
|
||||||
|
|
||||||
forEachGroup(target, (elem) => {
|
|
||||||
const name = elem.getAttribute('name')
|
|
||||||
params[name] = elem.getAttribute('closed') == '1' ? 'closed' : ''
|
|
||||||
})
|
|
||||||
|
|
||||||
forEachInput(target, (elem) => {
|
|
||||||
const name = elem.name
|
|
||||||
let value: RawCustomizerParams[string] = elem.value
|
|
||||||
if (elem.tagName == 'INPUT') {
|
|
||||||
if (elem.type == 'checkbox') value = elem?.checked
|
|
||||||
if (elem.type == 'range' || elem.type == 'color') applyRange(elem)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
numeric[elem.getAttribute('type')] ||
|
|
||||||
elem.getAttribute('numeric') == '1'
|
|
||||||
) {
|
|
||||||
value = parseFloat(String(value || 0))
|
|
||||||
} else if (
|
|
||||||
value &&
|
|
||||||
typeof value === 'string' &&
|
|
||||||
/^(\d+|\d+\.\d+)$/.test(value.trim())
|
|
||||||
) {
|
|
||||||
value = parseFloat(String(value || 0))
|
|
||||||
}
|
|
||||||
if (elem.type == 'radio' && !elem.checked) return // skip if not checked radio button
|
|
||||||
|
|
||||||
params[name] = value
|
|
||||||
})
|
|
||||||
return params
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useReducer } from 'react'
|
|||||||
import { cadPackages } from 'src/helpers/cadPackages'
|
import { cadPackages } from 'src/helpers/cadPackages'
|
||||||
import type { RootState } from '@react-three/fiber'
|
import type { RootState } from '@react-three/fiber'
|
||||||
import type { RawCustomizerParams } from 'src/helpers/cadPackages/common'
|
import type { RawCustomizerParams } from 'src/helpers/cadPackages/common'
|
||||||
|
import { CadhubParams } from 'src/components/Customizer/customizerConverter'
|
||||||
|
|
||||||
function withThunk(dispatch, getState) {
|
function withThunk(dispatch, getState) {
|
||||||
return (actionOrThunk) =>
|
return (actionOrThunk) =>
|
||||||
@@ -69,14 +70,14 @@ const main = ({length=200}) => {
|
|||||||
|
|
||||||
return [transpCube, star2D, line2D, ...logo]
|
return [transpCube, star2D, line2D, ...logo]
|
||||||
}
|
}
|
||||||
const getParameterDefinitions = ()=>{
|
const getParameterDefinitions = () => {
|
||||||
return [
|
return [
|
||||||
{type:'slider', name:'length', initial:200, caption:'Length', min:100, max:500},
|
{type:'slider', name:'length', initial:200, caption:'Length', min:100, max:500},
|
||||||
{ name: 'group1', type: 'group', caption: 'Group 1: Text Entry' },
|
{ name: 'group1', type: 'group', caption: 'Group 1: Text Entry' },
|
||||||
{ name: 'text', type: 'text', initial: '', size: 20, maxLength: 20, caption: 'Plain Text:', placeholder: '20 characters' },
|
{ name: 'text', type: 'text', initial: '', size: 20, maxLength: 20, caption: 'Hook’s “thickness” = object\’s width = print’s height', placeholder: '20 characters' },
|
||||||
{ name: 'int', type: 'int', initial: 20, min: 1, max: 100, step: 1, caption: 'Integer:' },
|
{ name: 'int', type: 'int', initial: 20, min: 1, max: 100, step: 1, caption: 'Integer:' },
|
||||||
{ name: 'number', type: 'number', initial: 2.0, min: 1.0, max: 10.0, step: 0.1, caption: 'Number:' },
|
{ name: 'number', type: 'number', initial: 2.0, min: 1.0, max: 10.0, step: 0.1, caption: 'Number:' },
|
||||||
{ name: 'date', type: 'date', initial: '2020-01-01', min: '2020-01-01', max: '2030-12-31', caption: 'Date:', placeholder: 'YYYY-MM-DD' },
|
{ name: 'date', type: 'date', initial: '2020-01-01', min: '2020-01-01', max: '2030-12-31', caption: 'Choose between classic hook with screw holes (0) or “bracket” system (1)', placeholder: 'YYYY-MM-DD' },
|
||||||
{ name: 'email', type: 'email', initial: 'me@example.com', caption: 'Email:' },
|
{ name: 'email', type: 'email', initial: 'me@example.com', caption: 'Email:' },
|
||||||
{ name: 'url', type: 'url', initial: 'www.example.com', size: 40, maxLength: 40, caption: 'Url:', placeholder: '40 characters' },
|
{ name: 'url', type: 'url', initial: 'www.example.com', size: 40, maxLength: 40, caption: 'Url:', placeholder: '40 characters' },
|
||||||
{ name: 'password', type: 'password', initial: '', caption: 'Password:' },
|
{ name: 'password', type: 'password', initial: '', caption: 'Password:' },
|
||||||
@@ -116,7 +117,7 @@ export interface State {
|
|||||||
data: any
|
data: any
|
||||||
quality: 'low' | 'high'
|
quality: 'low' | 'high'
|
||||||
}
|
}
|
||||||
customizerParams?: any[]
|
customizerParams: CadhubParams[]
|
||||||
currentParameters?: RawCustomizerParams
|
currentParameters?: RawCustomizerParams
|
||||||
layout: any
|
layout: any
|
||||||
camera: {
|
camera: {
|
||||||
@@ -152,6 +153,7 @@ export const initialState: State = {
|
|||||||
data: null,
|
data: null,
|
||||||
quality: 'low',
|
quality: 'low',
|
||||||
},
|
},
|
||||||
|
customizerParams: [],
|
||||||
layout: initialLayout,
|
layout: initialLayout,
|
||||||
camera: {},
|
camera: {},
|
||||||
viewerSize: { width: 0, height: 0 },
|
viewerSize: { width: 0, height: 0 },
|
||||||
@@ -175,11 +177,17 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
|
|||||||
case 'updateCode':
|
case 'updateCode':
|
||||||
return { ...state, code: payload }
|
return { ...state, code: payload }
|
||||||
case 'healthyRender':
|
case 'healthyRender':
|
||||||
const currentParameters =
|
const customizerParams: CadhubParams[] = payload?.customizerParams
|
||||||
payload.currentParameters &&
|
?.length
|
||||||
Object.keys(payload.currentParameters).length
|
? payload.customizerParams
|
||||||
? payload.currentParameters
|
: state.customizerParams
|
||||||
: state.currentParameters
|
const currentParameters = {}
|
||||||
|
customizerParams.forEach((param) => {
|
||||||
|
currentParameters[param.name] =
|
||||||
|
typeof state?.currentParameters?.[param.name] !== 'undefined'
|
||||||
|
? state?.currentParameters?.[param.name]
|
||||||
|
: param.initial
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
objectData: {
|
objectData: {
|
||||||
@@ -187,7 +195,7 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
|
|||||||
type: payload.objectData?.type,
|
type: payload.objectData?.type,
|
||||||
data: payload.objectData?.data,
|
data: payload.objectData?.data,
|
||||||
},
|
},
|
||||||
customizerParams: payload.customizerParams || state.customizerParams,
|
customizerParams,
|
||||||
currentParameters,
|
currentParameters,
|
||||||
consoleMessages: payload.message
|
consoleMessages: payload.message
|
||||||
? [...state.consoleMessages, payload.message]
|
? [...state.consoleMessages, payload.message]
|
||||||
@@ -203,7 +211,7 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
}
|
}
|
||||||
case 'setCurrentCustomizerParams':
|
case 'setCurrentCustomizerParams':
|
||||||
if (!Object.keys(payload).length) return state
|
if (!Object.keys(payload || {}).length) return state
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
currentParameters: payload,
|
currentParameters: payload,
|
||||||
|
|||||||
@@ -114,103 +114,3 @@ label {
|
|||||||
input.error, textarea.error {
|
input.error, textarea.error {
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
}
|
}
|
||||||
|
|
||||||
#jscad-customizer-block {
|
|
||||||
padding-bottom: 60px; /* hack because it gets cut off at the bottom for some reason*/
|
|
||||||
}
|
|
||||||
#jscad-customizer-block > .form-line{
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 5px 15px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
#jscad-customizer-block > .form-line:hover{
|
|
||||||
background: rgba(0,0,0,0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#jscad-customizer-block > .form-line[type="group"]{
|
|
||||||
background: rgba(255, 255, 255, 0.15);
|
|
||||||
padding-left: 50px;
|
|
||||||
padding-bottom: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
#jscad-customizer-block > .form-line[type="group"] > label{
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
#jscad-customizer-block > .form-line[closed="1"]:not([type="group"]){
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#jscad-customizer-block > .form-line[type="group"]:before {
|
|
||||||
position: absolute;
|
|
||||||
content: ">";
|
|
||||||
left: 18px;
|
|
||||||
top: 13px;
|
|
||||||
font-size: 30px;
|
|
||||||
transform: rotate(90deg);
|
|
||||||
font-family: monospace;
|
|
||||||
|
|
||||||
}
|
|
||||||
#jscad-customizer-block > .form-line[type="group"][closed="1"]:before {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#jscad-customizer-block > .form-line select,
|
|
||||||
#jscad-customizer-block > .form-line input[type="text"],
|
|
||||||
#jscad-customizer-block > .form-line input[type="range"],
|
|
||||||
#jscad-customizer-block > .form-line input[type="slider"],
|
|
||||||
#jscad-customizer-block > .form-line input[type="number"],
|
|
||||||
#jscad-customizer-block > .form-line input[type="int"],
|
|
||||||
#jscad-customizer-block > .form-line input[type="date"],
|
|
||||||
#jscad-customizer-block > .form-line input[type="email"],
|
|
||||||
#jscad-customizer-block > .form-line input[type="url"],
|
|
||||||
#jscad-customizer-block > .form-line input[type="password"]
|
|
||||||
{
|
|
||||||
background: rgba(73, 73, 73, 0.65);
|
|
||||||
border: 1px solid #FFFFFF;
|
|
||||||
width: 50%;
|
|
||||||
padding: 2px 8px;
|
|
||||||
}
|
|
||||||
#jscad-customizer-block > .form-line > div{
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
#jscad-customizer-block > .form-line > div[type="radio"] > label{
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
#jscad-customizer-block > .form-line[type="checkbox"] > label > input{
|
|
||||||
position: absolute;
|
|
||||||
right: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#jscad-customizer-block > .form-line > label i{
|
|
||||||
display: none;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
#jscad-customizer-block > .form-line[type="range"] > label i,
|
|
||||||
#jscad-customizer-block > .form-line[type="slider"] > label i,
|
|
||||||
#jscad-customizer-block > .form-line[type="color"] > label i{
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#jscad-customizer-block > .form-line input[type="range"]{
|
|
||||||
position: relative;
|
|
||||||
top: 6px;
|
|
||||||
}
|
|
||||||
#jscad-customizer-block > .form-line > label > i{
|
|
||||||
position: absolute;
|
|
||||||
top: 2px;
|
|
||||||
left: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#jscad-customizer-block .form-line label{
|
|
||||||
font-family: Fira Sans;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 14px;
|
|
||||||
|
|
||||||
color: #CFCFD8;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user
The following is just here to test implementation for OpenSCAD and will be removed later.