diff --git a/app/web/src/components/Customizer/Customizer.tsx b/app/web/src/components/Customizer/Customizer.tsx index 597e3aa..dca4b1c 100644 --- a/app/web/src/components/Customizer/Customizer.tsx +++ b/app/web/src/components/Customizer/Customizer.tsx @@ -1,6 +1,6 @@ -import { useRender, } from 'src/components/IdeWrapper/useRender' +import { useRender } from 'src/components/IdeWrapper/useRender' import { useIdeContext } from 'src/helpers/hooks/useIdeContext' -import { genParams, getParams } from 'src/helpers/cadPackages/jscadParams' +import { genParams, getParams } from 'src/helpers/cadPackages/jscadParams' const Customizer = () => { const [open, setOpen] = React.useState(true) @@ -11,17 +11,23 @@ const Customizer = () => { const customizerParams = state?.objectData?.customizerParams const lastParameters = state?.objectData?.lastParameters const handleRender = useRender() - const handleRender2 = ()=>handleRender(getParams(ref.current)) + const handleRender2 = () => handleRender(getParams(ref.current)) React.useEffect(() => { if (jsCadCustomizerElement && customizerParams) { - genParams(customizerParams, jsCadCustomizerElement, lastParameters || {}, (values, source)=>{ - if(source === 'group'){ - // save to local storage but do not render - return - } - if(checked) handleRender(values) - },[]) + genParams( + customizerParams, + jsCadCustomizerElement, + lastParameters || {}, + (values, source) => { + if (source === 'group') { + // save to local storage but do not render + return + } + if (checked) handleRender(values) + }, + [] + ) } }, [jsCadCustomizerElement, customizerParams, lastParameters, checked]) return ( @@ -37,11 +43,17 @@ const Customizer = () => {
Parameters
-
- { - const newValue = !checked - if (newValue) handleRender2() - setChecked(newValue)}}/> +
+ { + const newValue = !checked + if (newValue) handleRender2() + setChecked(newValue) + }} + />
-
diff --git a/app/web/src/helpers/cadPackages/common.ts b/app/web/src/helpers/cadPackages/common.ts index 5dab46d..4bdec2a 100644 --- a/app/web/src/helpers/cadPackages/common.ts +++ b/app/web/src/helpers/cadPackages/common.ts @@ -12,7 +12,7 @@ export const stlToGeometry = (url) => export interface RenderArgs { code: State['code'] - parameters: any, + parameters: any settings: { camera: State['camera'] viewerSize: State['viewerSize'] @@ -42,7 +42,7 @@ export function createHealthyResponse({ type, customizerParams, lastParameters, - }: { +}: { date: Date data: any consoleMessage: string diff --git a/app/web/src/helpers/cadPackages/jsCadController.ts b/app/web/src/helpers/cadPackages/jsCadController.ts index 5be10d1..b0c4ddc 100644 --- a/app/web/src/helpers/cadPackages/jsCadController.ts +++ b/app/web/src/helpers/cadPackages/jsCadController.ts @@ -86,7 +86,9 @@ export const render: DefaultKernelExport['render'] = async ({ settings, }: RenderArgs) => { if (!scriptWorker) { - console.trace('************************** creating new worker ************************') + console.trace( + '************************** creating new worker ************************' + ) const baseURI = document.baseURI.toString() const script = `let baseURI = '${baseURI}' importScripts(new URL('${scriptUrl}',baseURI)) @@ -127,14 +129,14 @@ export const render: DefaultKernelExport['render'] = async ({ scriptWorker.postMessage({ action: 'init', baseURI, alias: [] }) } - if(parameters){ + if (parameters) { // we are not evaluating code, but reacting to parameters change scriptWorker.postMessage({ action: 'updateParams', worker: 'script', params: parameters, }) - }else{ + } else { scriptWorker.postMessage({ action: 'runScript', worker: 'script', @@ -154,7 +156,7 @@ export const render: DefaultKernelExport['render'] = async ({ await waitResult resolveReference = null - if(parameters) delete response.customizerParams + if (parameters) delete response.customizerParams return response } diff --git a/app/web/src/helpers/cadPackages/jscadParams.js b/app/web/src/helpers/cadPackages/jscadParams.js index bd1e23a..66e6527 100644 --- a/app/web/src/helpers/cadPackages/jscadParams.js +++ b/app/web/src/helpers/cadPackages/jscadParams.js @@ -1,192 +1,215 @@ const GROUP_SELECTOR = 'DIV[type="group"]' const INPUT_SELECTOR = 'INPUT, SELECT' -function forEachInput(target, callback){ - target.querySelectorAll(INPUT_SELECTOR).forEach(callback) +function forEachInput(target, callback) { + target.querySelectorAll(INPUT_SELECTOR).forEach(callback) } -function forEachGroup(target, callback){ - target.querySelectorAll(GROUP_SELECTOR).forEach(callback) +function forEachGroup(target, callback) { + target.querySelectorAll(GROUP_SELECTOR).forEach(callback) } -const numeric = {number:1, float:1, int:1, range:1, slider:1} +const numeric = { number: 1, float: 1, int: 1, range: 1, slider: 1 } -function applyRange(inp){ - let label = inp.previousElementSibling - if(label && label.tagName == 'LABEL'){ - let info = label.querySelector('I') - if(info) info.innerHTML = inp.value +function applyRange(inp) { + let label = inp.previousElementSibling + if (label && label.tagName == 'LABEL') { + let info = label.querySelector('I') + if (info) info.innerHTML = inp.value + } +} + +export function genParams( + defs, + target, + storedParams = {}, + callback = undefined, + buttons = ['reset', 'save', 'load', 'edit', 'link'] +) { + let funcs = { + group: function ({ name, type, caption, captions, value, min, max }) { + return '' + }, + 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, + type, + caption, + captions, + value, + checked, + min, + max, + }) { + let 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++) { + let checked = value == values[i] || value == captions[i] ? 'checked' : '' + ret += `` } -} + return ret + '
' + } -export function genParams(defs, target, storedParams={}, callback=undefined, buttons=['reset','save','load','edit','link']){ + function inputChoice({ name, type, captions, value, values }) { + if (!captions) captions = values - let funcs = { - group:function({name,type, caption, captions, value, min,max}){ - return '' - }, - 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,type, caption, captions, value, checked, min,max}){ - let checkedStr = (value === 'checked' || value === true) ? 'checked':'' - return `` - }, - number: inputNumber, - } + let ret = `' + } - 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' + var str = `' + } - for(let i =0; i${captions[i]}` - } - return ret +'
' - } - - function inputChoice({name, type, captions, value, values}){ - if(!captions) captions = values + let html = '' + let closed = false + let missing = {} - let ret = `' - } + if (storedParams[name] !== undefined) { + def.value = storedParams[name] + } else { + def.value = def.initial || def['default'] || def.checked + } - 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' - var str = `'; - } + if (type == 'group') { + closed = def.value == 'closed' + } + def.closed = closed - let html = ''; - let closed = false - let missing = {} + html += `
` - defs.forEach(def=>{ - let {type, caption, name} = def + html += `` + if (type == 'checkbox') html += funcs[type](def) + html += `${caption}${def.value}` - if(storedParams[name] !== undefined){ - def.value = storedParams[name]; - }else { - def.value = def.initial || def['default'] || def.checked - } + if (funcs[type] && type != 'checkbox') html += funcs[type](def) - if(type == 'group'){ - closed = def.value == 'closed' - } - def.closed = closed - - html +=`
` - - html += `` - if(type == 'checkbox') html += funcs[type](def) - html += `${caption}${def.value}` - - if(funcs[type] && type != 'checkbox') html += funcs[type](def) + if (!funcs[type]) missing[type] = 1 - if(!funcs[type]) missing[type] = 1 - - html +='
\n' - }) + html += '
\n' + }) - let missingKeys = Object.keys(missing) - if(missingKeys.length) console.log('missing param impl',missingKeys); + let missingKeys = Object.keys(missing) + if (missingKeys.length) console.log('missing param impl', missingKeys) - function _callback(source='change'){ - if(callback) callback(getParams(target), source) - } + function _callback(source = 'change') { + if (callback) callback(getParams(target), source) + } - html +='
' - buttons.forEach(b=>{ - if(typeof b === 'string') b = {id:b, name:b} - let {id,name} = b - html += `` - }) - html += '
' + html += '
' + buttons.forEach((b) => { + if (typeof b === 'string') b = { id: b, name: b } + let { id, name } = b + html += `` + }) + html += '
' - target.innerHTML = html + target.innerHTML = html - forEachInput(target, inp=>{ - let 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) - - }) - - function groupClick(evt){ - var groupDiv = evt.target - if(groupDiv.tagName === 'LABEL') groupDiv = groupDiv.parentNode - var closed = (groupDiv.getAttribute('closed') == '1') ? '0':'1' - var name = evt.target.getAttribute('name') - do{ - groupDiv.setAttribute('closed', closed) - groupDiv = groupDiv.nextElementSibling - }while(groupDiv && groupDiv.getAttribute('type') != 'group') - _callback('group') - } - - forEachGroup(target, div=>{ - div.onclick=groupClick - }) -} - -export function getParams(target){ - let params = {} - if(!target) return params - - forEachGroup(target,elem=>{ - let name = elem.getAttribute('name') - params[name] = (elem.getAttribute('closed') == '1') ? 'closed':'' + forEachInput(target, (inp) => { + let 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) + }) - forEachInput(target,elem=>{ - let name = elem.name - let value = elem.value - if(elem.tagName == 'INPUT'){ - if(elem.type == 'checkbox') value = elem.checked - if(elem.type == 'range' || elem.type == 'color') applyRange(elem) - } + function groupClick(evt) { + var groupDiv = evt.target + if (groupDiv.tagName === 'LABEL') groupDiv = groupDiv.parentNode + var closed = groupDiv.getAttribute('closed') == '1' ? '0' : '1' + var name = evt.target.getAttribute('name') + do { + groupDiv.setAttribute('closed', closed) + groupDiv = groupDiv.nextElementSibling + } while (groupDiv && groupDiv.getAttribute('type') != 'group') + _callback('group') + } - if(numeric[elem.getAttribute('type')] || elem.getAttribute('numeric') == '1') value = parseFloat(value || 0) - - if(elem.type == 'radio' && !elem.checked) return // skip if not checked radio button - - params[name] = value - }) - return params; + forEachGroup(target, (div) => { + div.onclick = groupClick + }) } +export function getParams(target) { + let params = {} + if (!target) return params + + forEachGroup(target, (elem) => { + let name = elem.getAttribute('name') + params[name] = elem.getAttribute('closed') == '1' ? 'closed' : '' + }) + + forEachInput(target, (elem) => { + let name = elem.name + let value = 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(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/useIdeState.ts b/app/web/src/helpers/hooks/useIdeState.ts index d4f5200..2de05d4 100644 --- a/app/web/src/helpers/hooks/useIdeState.ts +++ b/app/web/src/helpers/hooks/useIdeState.ts @@ -180,7 +180,8 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => { ...state.objectData, type: payload.objectData?.type, data: payload.objectData?.data, - customizerParams: payload.customizerParams || state.objectData.customizerParams, + customizerParams: + payload.customizerParams || state.objectData.customizerParams, lastParameters: payload.lastParameters, }, consoleMessages: payload.message @@ -246,7 +247,7 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => { interface RequestRenderArgs { state: State dispatch: any - parameters: any, + parameters: any code: State['code'] camera: State['camera'] viewerSize: State['viewerSize'] @@ -280,26 +281,28 @@ export const requestRender = ({ quality, }, }) - .then(({ objectData, message, status, customizerParams, lastParameters }) => { - if (status === 'error') { - dispatch({ - type: 'errorRender', - payload: { message }, - }) - } else { - dispatch({ - type: 'healthyRender', - payload: { - objectData, - message, - lastRunCode: code, - customizerParams, - lastParameters, - }, - }) - return objectData + .then( + ({ objectData, message, status, customizerParams, lastParameters }) => { + if (status === 'error') { + dispatch({ + type: 'errorRender', + payload: { message }, + }) + } else { + dispatch({ + type: 'healthyRender', + payload: { + objectData, + message, + lastRunCode: code, + customizerParams, + lastParameters, + }, + }) + return objectData + } } - }) + ) .catch(() => dispatch({ type: 'resetLoading' })) // TODO should probably display something to the user here } }