diff --git a/.vscode/settings.json b/.vscode/settings.json
index d71538b..e9d5f61 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,6 @@
{
"cSpell.words": [
+ "Customizer",
"Hutten",
"cadquery",
"jscad",
diff --git a/app/web/public/demo-worker.js b/app/web/public/demo-worker.js
index ebbb97c..10e086c 100644
--- a/app/web/public/demo-worker.js
+++ b/app/web/public/demo-worker.js
@@ -246,6 +246,7 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
function runMain(params={}){
let time = Date.now()
let solids
+ let transfer = []
try{
solids = main(params)
}catch(e){
@@ -255,7 +256,6 @@ const makeScriptWorker = ({callback, convertToSolids})=>{
let solidsTime = Date.now() - time
scriptStats = `generate solids ${solidsTime}ms`
- let transfer = []
if(convertToSolids === 'buffers'){
CSGToBuffers.clearCache()
entities = solids.map((csg)=>{
@@ -486,7 +486,6 @@ let perspectiveCamera
let time = Date.now()
renderer(renderOptions)
if(updateRender){
- console.log(updateRender, ' first render', Date.now()-time);
updateRender = '';
}
}
@@ -568,7 +567,6 @@ return (params)=>{
const makeRenderWorkerHere = (scope === 'main' && canvas && !renderInWorker) || (scope === 'worker' && render)
// worker is in current thread
if(makeRenderWorkerHere){
- console.log('render in scope: '+scope);
renderWorker = makeRenderWorker({callback:sendCmd})
sendToRender = (params, transfer)=>renderWorker.postMessage(params, transfer)
}
diff --git a/app/web/src/components/Customizer/Customizer.tsx b/app/web/src/components/Customizer/Customizer.tsx
new file mode 100644
index 0000000..d418538
--- /dev/null
+++ b/app/web/src/components/Customizer/Customizer.tsx
@@ -0,0 +1,100 @@
+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'
+
+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 handleRender = useRender()
+
+ React.useEffect(() => {
+ if (jsCadCustomizerElement && customizerParams) {
+ genParams(
+ customizerParams,
+ jsCadCustomizerElement,
+ currentParameters || {},
+ (values, source) => {
+ thunkDispatch({ type: 'setCurrentCustomizerParams', payload: values })
+ if (shouldLiveUpdate) {
+ handleRender()
+ }
+ },
+ []
+ )
+ }
+ }, [
+ jsCadCustomizerElement,
+ customizerParams,
+ currentParameters,
+ shouldLiveUpdate,
+ ])
+ if (!state.customizerParams) return null
+ return (
+
+
+
+
+
Parameters
+
+ {open && (
+ <>
+
+
Auto Update
+
{
+ setShouldLiveUpdate
+ if (newValue) handleRender()
+ setShouldLiveUpdate(newValue)
+ }}
+ className={`${
+ shouldLiveUpdate ? 'bg-ch-purple-600' : 'bg-ch-gray-300'
+ } relative inline-flex items-center h-6 rounded-full w-11 mr-6`}
+ >
+
+
+
+
+ >
+ )}
+
+
+
+ )
+}
+
+export default Customizer
diff --git a/app/web/src/components/DelayedPingAnimation/DelayedPingAnimation.tsx b/app/web/src/components/DelayedPingAnimation/DelayedPingAnimation.tsx
new file mode 100644
index 0000000..bfc4995
--- /dev/null
+++ b/app/web/src/components/DelayedPingAnimation/DelayedPingAnimation.tsx
@@ -0,0 +1,29 @@
+
+
+let timeoutId = 0
+const DelayedPingAnimation = ({isLoading: isLoading}: {isLoading: boolean}) => {
+ const [showLoading, setShowLoading] = React.useState(false)
+ React.useEffect(() => {
+ if (!isLoading && showLoading) {
+ setShowLoading(isLoading)
+ clearTimeout(timeoutId)
+ } else if (isLoading && !showLoading) {
+ timeoutId = setTimeout(() => {
+ setShowLoading(isLoading)
+ console.log('setloading')
+ }, 300) as unknown as number
+ } else if (!isLoading) {
+ setShowLoading(isLoading)
+ clearTimeout(timeoutId)
+ }
+ }, [isLoading])
+
+ if (showLoading && isLoading) return (
+
+ )
+ return null
+}
+
+export default DelayedPingAnimation
diff --git a/app/web/src/components/EditorMenu/helpers.ts b/app/web/src/components/EditorMenu/helpers.ts
index 0ced278..6caaa14 100644
--- a/app/web/src/components/EditorMenu/helpers.ts
+++ b/app/web/src/components/EditorMenu/helpers.ts
@@ -74,6 +74,7 @@ export const makeStlDownloadHandler =
camera: state.camera,
quality: 'high',
specialCadProcess,
+ parameters: state.currentParameters,
}).then((result) => result && saveFile(result.data))
})
}
diff --git a/app/web/src/components/IdeEditor/IdeEditor.tsx b/app/web/src/components/IdeEditor/IdeEditor.tsx
index 2a514f5..1a5f6c6 100644
--- a/app/web/src/components/IdeEditor/IdeEditor.tsx
+++ b/app/web/src/components/IdeEditor/IdeEditor.tsx
@@ -62,6 +62,7 @@ const IdeEditor = ({ Loading }) => {
code: state.code,
viewerSize: state.viewerSize,
camera: state.camera,
+ parameters: state.currentParameters,
})
})
localStorage.setItem(makeCodeStoreKey(state.ideType), state.code)
diff --git a/app/web/src/components/IdeViewer/IdeViewer.tsx b/app/web/src/components/IdeViewer/IdeViewer.tsx
index 8930b9f..13d1db7 100644
--- a/app/web/src/components/IdeViewer/IdeViewer.tsx
+++ b/app/web/src/components/IdeViewer/IdeViewer.tsx
@@ -6,6 +6,8 @@ import { Vector3 } from 'three'
import { requestRender } from 'src/helpers/hooks/useIdeState'
import texture from './dullFrontLitMetal.png'
import { TextureLoader } from 'three/src/loaders/TextureLoader'
+import Customizer from 'src/components/Customizer/Customizer'
+ import DelayedPingAnimation from 'src/components/DelayedPingAnimation/DelayedPingAnimation'
const loader = new TextureLoader()
const colorMap = loader.load(texture)
@@ -210,6 +212,7 @@ const IdeViewer = ({ Loading }) => {
code: state.code,
viewerSize: state.viewerSize,
camera,
+ parameters: state.currentParameters,
})
}
})
@@ -238,11 +241,8 @@ const IdeViewer = ({ Loading }) => {
/>
- {state.isLoading && (
-
- )}
+
+
)
}
diff --git a/app/web/src/components/IdeWrapper/useRender.ts b/app/web/src/components/IdeWrapper/useRender.ts
index bc82f81..4ac6312 100644
--- a/app/web/src/components/IdeWrapper/useRender.ts
+++ b/app/web/src/components/IdeWrapper/useRender.ts
@@ -13,6 +13,7 @@ export const useRender = () => {
code: state.code,
viewerSize: state.viewerSize,
camera: state.camera,
+ parameters: state.currentParameters,
})
})
localStorage.setItem(makeCodeStoreKey(state.ideType), state.code)
diff --git a/app/web/src/helpers/cadPackages/common.ts b/app/web/src/helpers/cadPackages/common.ts
index cbe789a..1a5acc1 100644
--- a/app/web/src/helpers/cadPackages/common.ts
+++ b/app/web/src/helpers/cadPackages/common.ts
@@ -12,6 +12,7 @@ export const stlToGeometry = (url) =>
export interface RenderArgs {
code: State['code']
+ parameters?: RawCustomizerParams
settings: {
camera: State['camera']
viewerSize: State['viewerSize']
@@ -30,6 +31,12 @@ export interface HealthyResponse {
data: any
type: 'stl' | 'png' | 'geometry'
}
+ customizerParams?: any[]
+ currentParameters?: RawCustomizerParams
+}
+
+export interface RawCustomizerParams {
+ [paramName: string]: number | string | boolean
}
export function createHealthyResponse({
@@ -37,11 +44,15 @@ export function createHealthyResponse({
data,
consoleMessage,
type,
+ customizerParams,
+ currentParameters,
}: {
date: Date
data: any
consoleMessage: string
type: HealthyResponse['objectData']['type']
+ customizerParams?: any
+ currentParameters?: any
}): HealthyResponse {
return {
status: 'healthy',
@@ -54,6 +65,8 @@ export function createHealthyResponse({
message: consoleMessage,
time: date,
},
+ customizerParams,
+ currentParameters,
}
}
diff --git a/app/web/src/helpers/cadPackages/index.ts b/app/web/src/helpers/cadPackages/index.ts
index 145717e..568e9b9 100644
--- a/app/web/src/helpers/cadPackages/index.ts
+++ b/app/web/src/helpers/cadPackages/index.ts
@@ -3,7 +3,7 @@ import type { CadPackage } from 'src/helpers/hooks/useIdeState'
import openscad from './openScadController'
import cadquery from './cadQueryController'
-import jscad from './jsCadController'
+import jscad from './jsCad/jsCadController'
export const cadPackages: { [key in CadPackage]: DefaultKernelExport } = {
openscad,
diff --git a/app/web/src/helpers/cadPackages/jsCadController.ts b/app/web/src/helpers/cadPackages/jsCad/jsCadController.ts
similarity index 66%
rename from app/web/src/helpers/cadPackages/jsCadController.ts
rename to app/web/src/helpers/cadPackages/jsCad/jsCadController.ts
index e26f92e..f0088da 100644
--- a/app/web/src/helpers/cadPackages/jsCadController.ts
+++ b/app/web/src/helpers/cadPackages/jsCad/jsCadController.ts
@@ -3,7 +3,7 @@ import {
DefaultKernelExport,
createUnhealthyResponse,
createHealthyResponse,
-} from './common'
+} from '../common'
import {
MeshPhongMaterial,
LineBasicMaterial,
@@ -70,6 +70,7 @@ function CSG2Object3D(obj) {
}
let scriptWorker
+let currentParameters = {}
const scriptUrl = '/demo-worker.js'
let resolveReference = null
let response = null
@@ -81,25 +82,32 @@ const callResolve = () => {
export const render: DefaultKernelExport['render'] = async ({
code,
+ parameters,
settings,
}: RenderArgs) => {
if (!scriptWorker) {
+ console.trace(
+ '************************** creating new worker ************************'
+ )
const baseURI = document.baseURI.toString()
const script = `let baseURI = '${baseURI}'
-importScripts(new URL('${scriptUrl}',baseURI))
-let worker = jscadWorker({
- baseURI: baseURI,
- scope:'worker',
- convertToSolids: 'buffers',
- callback:(params)=>self.postMessage(params),
-})
-self.addEventListener('message', (e)=>worker.postMessage(e.data))
-`
+ importScripts(new URL('${scriptUrl}',baseURI))
+ let worker = jscadWorker({
+ baseURI: baseURI,
+ scope:'worker',
+ convertToSolids: 'buffers',
+ callback:(params)=>self.postMessage(params),
+ })
+ self.addEventListener('message', (e)=>worker.postMessage(e.data))
+ `
const blob = new Blob([script], { type: 'text/javascript' })
scriptWorker = new Worker(window.URL.createObjectURL(blob))
+ let parameterDefinitions = []
scriptWorker.addEventListener('message', (e) => {
const data = e.data
- if (data.action == 'entities') {
+ if (data.action == 'parameterDefinitions') {
+ parameterDefinitions = data.data
+ } else if (data.action == 'entities') {
if (data.error) {
response = createUnhealthyResponse(new Date(), data.error)
} else {
@@ -108,6 +116,8 @@ self.addEventListener('message', (e)=>worker.postMessage(e.data))
data: [...data.entities.map(CSG2Object3D).filter((o) => o)],
consoleMessage: data.scriptStats,
date: new Date(),
+ customizerParams: parameterDefinitions,
+ currentParameters,
})
}
callResolve()
@@ -118,12 +128,27 @@ self.addEventListener('message', (e)=>worker.postMessage(e.data))
response = null
scriptWorker.postMessage({ action: 'init', baseURI, alias: [] })
}
- scriptWorker.postMessage({
- action: 'runScript',
- worker: 'script',
- script: code,
- url: 'jscad_script',
- })
+
+ if (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
@@ -131,6 +156,7 @@ self.addEventListener('message', (e)=>worker.postMessage(e.data))
await waitResult
resolveReference = null
+ if (parameters) delete response.customizerParams
return response
}
diff --git a/app/web/src/helpers/cadPackages/jsCad/jscadParams.ts b/app/web/src/helpers/cadPackages/jsCad/jscadParams.ts
new file mode 100644
index 0000000..503f9f1
--- /dev/null
+++ b/app/web/src/helpers/cadPackages/jsCad/jscadParams.ts
@@ -0,0 +1,210 @@
+import type { RawCustomizerParams } from '../common'
+
+const GROUP_SELECTOR = 'DIV[type="group"]'
+const INPUT_SELECTOR = 'INPUT, SELECT'
+
+function forEachInput(
+ target: HTMLElement,
+ callback: (e: HTMLInputElement) => void
+) {
+ target.querySelectorAll(INPUT_SELECTOR).forEach(callback)
+}
+
+function forEachGroup(target: HTMLElement, callback: (e: HTMLElement) => void) {
+ target.querySelectorAll(GROUP_SELECTOR).forEach(callback)
+}
+
+const numeric = { number: 1, float: 1, int: 1, range: 1, slider: 1 }
+
+function applyRange(inp) {
+ const label = inp.previousElementSibling
+ if (label && label.tagName == 'LABEL') {
+ const info = label.querySelector('I')
+ if (info) info.innerHTML = inp.value
+ }
+}
+
+export function genParams(
+ defs,
+ target,
+ storedParams = {},
+ callback: (values: RawCustomizerParams, source: any) => void = undefined,
+ buttons = ['reset', 'save', 'load', 'edit', 'link']
+) {
+ const funcs = {
+ group: () => '',
+ choice: inputChoice,
+ radio: inputRadio,
+ float: inputNumber,
+ range: inputNumber,
+ slider: inputNumber,
+ int: inputNumber,
+ text: inputNumber,
+ url: inputNumber,
+ email: inputNumber,
+ date: inputNumber,
+ password: inputNumber,
+ color: inputNumber,
+ // TODO radio similar options as choice
+ checkbox: function ({ name, value }) {
+ const checkedStr = value === 'checked' || value === true ? 'checked' : ''
+ return ``
+ },
+ number: inputNumber,
+ }
+
+ function inputRadio({ name, type, captions, value, values }) {
+ if (!captions) captions = values
+
+ let ret = ''
+
+ for (let i = 0; i < values.length; i++) {
+ const checked =
+ value == values[i] || value == captions[i] ? 'checked' : ''
+ ret += ``
+ }
+ return ret + '
'
+ }
+
+ function inputChoice({ name, type, captions, value, values }) {
+ if (!captions) captions = values
+
+ let ret = `'
+ }
+
+ 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 = `'
+ }
+
+ 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 += ``
+
+ html += ``
+
+ if (funcs[type] && type != 'checkbox') html += funcs[type](def)
+
+ if (!funcs[type]) missing[type] = 1
+
+ html += '
\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 += ''
+
+ 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')
+ 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))
+
+ if (elem.type == 'radio' && !elem.checked) return // skip if not checked radio button
+
+ params[name] = value
+ })
+ return params
+}
diff --git a/app/web/src/helpers/hooks/use3dViewerResize.ts b/app/web/src/helpers/hooks/use3dViewerResize.ts
index 9ed6321..dd5fe24 100644
--- a/app/web/src/helpers/hooks/use3dViewerResize.ts
+++ b/app/web/src/helpers/hooks/use3dViewerResize.ts
@@ -26,6 +26,7 @@ export const use3dViewerResize = () => {
code: state.code,
viewerSize: { width, height },
camera: state.camera,
+ parameters: state.currentParameters,
})
}
})
diff --git a/app/web/src/helpers/hooks/useIdeState.ts b/app/web/src/helpers/hooks/useIdeState.ts
index 9b671be..bd157b1 100644
--- a/app/web/src/helpers/hooks/useIdeState.ts
+++ b/app/web/src/helpers/hooks/useIdeState.ts
@@ -1,6 +1,7 @@
import { useReducer } from 'react'
import { cadPackages } from 'src/helpers/cadPackages'
import type { RootState } from '@react-three/fiber'
+import type { RawCustomizerParams } from 'src/helpers/cadPackages/common'
function withThunk(dispatch, getState) {
return (actionOrThunk) =>
@@ -43,13 +44,14 @@ result = (cq.Workplane().circle(diam).extrude(20.0)
show_object(result)
`,
jscad: `
+
const { booleans, colors, primitives } = require('@jscad/modeling') // modeling comes from the included MODELING library
const { intersect, subtract } = booleans
const { colorize } = colors
const { cube, cuboid, line, sphere, star } = primitives
-const main = ({scale=1}) => {
+const main = ({length=340}) => {
const logo = [
colorize([1.0, 0.4, 1.0], subtract(
cube({ size: 300 }),
@@ -61,13 +63,37 @@ const main = ({scale=1}) => {
))
]
- const transpCube = colorize([1, 0, 0, 0.75], cuboid({ size: [100 * scale, 100, 210 + (200 * scale)] }))
+ const transpCube = colorize([1, 0, 0, 0.75], cuboid({ size: [100, 100, length] }))
const star2D = star({ vertices: 8, innerRadius: 150, outerRadius: 200 })
const line2D = colorize([1.0, 0, 0], line([[220, 220], [-220, 220], [-220, -220], [220, -220], [220, 220]]))
return [transpCube, star2D, line2D, ...logo]
}
-module.exports = {main}
+const getParameterDefinitions = ()=>{
+ return [
+ {type:'slider', name:'length', initial:340, caption:'Length', min:210, max:1500},
+ { 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: '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: 'date', type: 'date', initial: '2020-01-01', min: '2020-01-01', max: '2030-12-31', caption: 'Date:', placeholder: 'YYYY-MM-DD' },
+ { 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: 'password', type: 'password', initial: '', caption: 'Password:' },
+
+ { name: 'group2', type: 'group', caption: 'Group 2: Interactive Controls' },
+ { name: 'checkbox', type: 'checkbox', checked: true, initial: '20', caption: 'Checkbox:' },
+ { name: 'color', type: 'color', initial: '#FFB431', caption: 'Color:' },
+ { name: 'slider', type: 'slider', initial: 3, min: 1, max: 10, step: 1, caption: 'Slider:' },
+ { name: 'choice1', type: 'choice', caption: 'Dropdown Menu:', values: [0, 1, 2, 3], captions: ['No', 'Yes', 'Maybe', 'So so'], initial: 2 },
+ { name: 'choice3', type: 'choice', caption: 'Dropdown Menu:', values: ['No', 'Yes', 'Maybe', 'So so'], initial: 'No' },
+ { name: 'choice2', type: 'radio', caption: 'Radio Buttons:', values:[0, 1, 2, 3], captions: ['No', 'Yes', 'Maybe', 'So so'], initial: 2 },
+
+ { name: 'group3', type: 'group', initial: 'closed', caption: 'Group 3: Initially Closed Group' },
+ { name: 'checkbox2', type: 'checkbox', checked: true, initial: '20', caption: 'Optional Checkbox:' },
+ ]
+}
+module.exports = {main, getParameterDefinitions}
`,
}
@@ -90,6 +116,8 @@ export interface State {
data: any
quality: 'low' | 'high'
}
+ customizerParams?: any[]
+ currentParameters?: RawCustomizerParams
layout: any
camera: {
dist?: number
@@ -147,6 +175,7 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
case 'updateCode':
return { ...state, code: payload }
case 'healthyRender':
+ const currentParameters = (payload.currentParameters && Object.keys(payload.currentParameters).length) ? payload.currentParameters : state.currentParameters
return {
...state,
objectData: {
@@ -154,6 +183,8 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
type: payload.objectData?.type,
data: payload.objectData?.data,
},
+ customizerParams: payload.customizerParams || state.customizerParams,
+ currentParameters,
consoleMessages: payload.message
? [...state.consoleMessages, payload.message]
: payload.message,
@@ -167,6 +198,12 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
: payload.message,
isLoading: false,
}
+ case 'setCurrentCustomizerParams':
+ if (!Object.keys(payload).length) return state
+ return {
+ ...state,
+ currentParameters: payload,
+ }
case 'setLayout':
return {
...state,
@@ -217,6 +254,7 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => {
interface RequestRenderArgs {
state: State
dispatch: any
+ parameters: any
code: State['code']
camera: State['camera']
viewerSize: State['viewerSize']
@@ -232,6 +270,7 @@ export const requestRender = ({
viewerSize,
quality = 'low',
specialCadProcess = null,
+ parameters,
}: RequestRenderArgs) => {
if (
state.ideType !== 'INIT' &&
@@ -242,26 +281,41 @@ export const requestRender = ({
: cadPackages[state.ideType].render
return renderFn({
code,
+ parameters,
settings: {
camera,
viewerSize,
quality,
},
})
- .then(({ objectData, message, status }) => {
- if (status === 'error') {
- dispatch({
- type: 'errorRender',
- payload: { message },
- })
- } else {
- dispatch({
- type: 'healthyRender',
- payload: { objectData, message, lastRunCode: code },
- })
- return objectData
+ .then(
+ ({
+ objectData,
+ message,
+ status,
+ customizerParams,
+ currentParameters,
+ }) => {
+ if (status === 'error') {
+ dispatch({
+ type: 'errorRender',
+ payload: { message },
+ })
+ } else {
+ dispatch({
+ type: 'healthyRender',
+ payload: {
+ objectData,
+ message,
+ lastRunCode: code,
+ customizerParams,
+ currentParameters,
+ },
+ })
+ return objectData
+ }
}
- })
+ )
.catch(() => dispatch({ type: 'resetLoading' })) // TODO should probably display something to the user here
}
}
diff --git a/app/web/src/index.css b/app/web/src/index.css
index 66183b1..7d3b8c9 100644
--- a/app/web/src/index.css
+++ b/app/web/src/index.css
@@ -115,3 +115,102 @@ input.error, textarea.error {
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;
+}
\ No newline at end of file