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',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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