diff --git a/.vscode/settings.json b/.vscode/settings.json index e9d5f61..aaa161b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ + "Cadhub", "Customizer", "Hutten", "cadquery", diff --git a/app/web/src/components/Customizer/Customizer.tsx b/app/web/src/components/Customizer/Customizer.tsx index ced655e..53f170e 100644 --- a/app/web/src/components/Customizer/Customizer.tsx +++ b/app/web/src/components/Customizer/Customizer.tsx @@ -1,41 +1,30 @@ import { useRender } from 'src/components/IdeWrapper/useRender' import { useIdeContext } from 'src/helpers/hooks/useIdeContext' -import { genParams } from 'src/helpers/cadPackages/jsCad/jscadParams' import { Switch } from '@headlessui/react' import Svg from 'src/components/Svg/Svg' +import { + CadhubStringParam, + CadhubBooleanParam, + CadhubNumberParam, +} from './customizerConverter' const Customizer = () => { const [open, setOpen] = React.useState(false) const [shouldLiveUpdate, setShouldLiveUpdate] = React.useState(false) - const ref = React.useRef() - const jsCadCustomizerElement = ref.current const { state, thunkDispatch } = useIdeContext() const customizerParams = state?.customizerParams - const currentParameters = state?.currentParameters + const currentParameters = state?.currentParameters || {} const handleRender = useRender() - React.useEffect(() => { - if (jsCadCustomizerElement && customizerParams) { - genParams( - customizerParams, - jsCadCustomizerElement, - currentParameters || {}, - (values, source) => { - thunkDispatch({ type: 'setCurrentCustomizerParams', payload: values }) - if (shouldLiveUpdate) { - handleRender() - } - }, - [] - ) + const updateCustomizerParam = (paramName: string, paramValue: any) => { + const payload = { + ...currentParameters, + [paramName]: paramValue, } - }, [ - jsCadCustomizerElement, - customizerParams, - currentParameters, - shouldLiveUpdate, - ]) - if (!state.customizerParams) return null + thunkDispatch({ type: 'setCurrentCustomizerParams', payload }) + shouldLiveUpdate && setTimeout(() => handleRender()) + } + if (!customizerParams?.length) return null return (
{ { - setShouldLiveUpdate if (newValue) handleRender() setShouldLiveUpdate(newValue) }} @@ -86,15 +74,184 @@ const Customizer = () => { )}
-
-
+
+
+ {customizerParams.map((param, index) => { + const otherProps = { + value: currentParameters[param.name], + onChange: (value) => updateCustomizerParam(param.name, value), + } + if (param.type === 'string') { + return + } else if (param.type === 'number') { + return + } else if (param.type === 'boolean') { + return + } + return
{JSON.stringify(param)}
+ })} +
) } export default Customizer + +function CustomizerParamBase({ + name, + caption, + children, +}: { + name: string + caption: string + children: React.ReactNode +}) { + return ( +
  • +
    +
    {name}
    +
    {caption}
    +
    +
    {children}
    +
  • + ) +} + +function BooleanParam({ + param, + value, + onChange, +}: { + param: CadhubBooleanParam + value: any + onChange: Function +}) { + return ( + + { + onChange(newValue) + }} + className={`${ + value ? 'bg-ch-gray-300' : 'bg-ch-gray-600' + } relative inline-flex items-center h-6 rounded-full w-11 mr-6 border border-ch-gray-300`} + > + + + + ) +} + +function StringParam({ + param, + value, + onChange, +}: { + param: CadhubStringParam + value: any + onChange: Function +}) { + return ( + + onChange(target?.value)} + /> + + ) +} + +function NumberParam({ + param, + value, + onChange, +}: { + param: CadhubNumberParam + value: any + onChange: Function +}) { + const [isFocused, isFocusedSetter] = React.useState(false) + const [localValue, localValueSetter] = React.useState(0) + const [isLocked, isLockedSetter] = React.useState(false) + const [pixelsDragged, pixelsDraggedSetter] = React.useState(0) + const step = param.step || 1 + const commitChange = () => { + let num = localValue + if (typeof param.step === 'number') { + num = Math.round(num / step) * step + } + if (typeof param.min === 'number') { + num = Math.max(param.min, num) + } + if (typeof param.max === 'number') { + num = Math.min(param.max, num) + } + num = Number(num.toFixed(2)) + localValueSetter(num) + onChange(num) + } + React.useEffect(() => { + if (!isFocused) commitChange() + }, [isFocused]) + React.useEffect(() => localValueSetter(value), [value]) + return ( + +
    + localValue) + ? 'text-red-500' + : '' + }`} + type="number" + value={localValue} + onFocus={() => isFocusedSetter(true)} + onBlur={() => isFocusedSetter(false)} + onKeyDown={({ key }) => key === 'Enter' && commitChange()} + onChange={({ target }) => { + const num = Number(target?.value) + localValueSetter(num) + }} + max={param.max} + min={param.min} + step={step} + /> +
    { + isLockedSetter(true) + target?.requestPointerLock?.() + pixelsDraggedSetter(localValue) + }} + onMouseUp={() => { + isLockedSetter(false) + document?.exitPointerLock?.() + commitChange() + }} + onMouseMove={({ movementX }) => { + if (isLocked && movementX) { + pixelsDraggedSetter(pixelsDragged + (movementX * step) / 8) // one step per 8 pixels + localValueSetter(Number(pixelsDragged.toFixed(2))) + } + }} + > + +
    +
    +
    + ) +} diff --git a/app/web/src/components/Customizer/customizerConverter.ts b/app/web/src/components/Customizer/customizerConverter.ts new file mode 100644 index 0000000..fee6128 --- /dev/null +++ b/app/web/src/components/Customizer/customizerConverter.ts @@ -0,0 +1,120 @@ +// CadHub + +type CadhubTypeNames = 'number' | 'string' | 'boolean' + +interface CadhubParamBase { + type: CadhubTypeNames + caption: string + name: string +} +export interface CadhubStringParam extends CadhubParamBase { + type: 'string' + initial: string + placeholder?: string + maxLength?: number +} +export interface CadhubBooleanParam extends CadhubParamBase { + type: 'boolean' + initial?: boolean +} +export interface CadhubNumberParam extends CadhubParamBase { + type: 'number' + initial: number + min?: number + max?: number + step?: number + decimal?: number +} + +export type CadhubParams = + | CadhubStringParam + | CadhubBooleanParam + | CadhubNumberParam + +// OpenSCAD +const openscadValues = ` +// slider widget for number with max. value +sliderWithMax =34; // [50] + +// slider widget for number in range +sliderWithRange =34; // [10:100] + +//step slider for number +stepSlider=2; //[0:5:100] + +// slider widget for number in range +sliderCentered =0; // [-10:0.1:10] + +// spinbox with step size 1 +Spinbox= 5; + +// Text box for string +String="hello"; + +// Text box for string with length 8 +String2="length"; //8 + +//description +Variable = true; +` + +const openscadConverted: CadhubParams[] = [ + { + type: 'number', + name: 'sliderWithMax', + caption: 'slider widget for number with max. value', + initial: 34, + step: 1, + max: 50, + }, + { + type: 'number', + name: 'sliderWithRange', + caption: 'slider widget for number in range', + initial: 34, + step: 1, + min: 10, + max: 100, + }, + { + type: 'number', + name: 'stepSlider', + caption: 'step slider for number', + initial: 2, + step: 5, + min: 0, + max: 100, + }, + { + type: 'number', + name: 'sliderCentered', + caption: 'slider widget for number in range', + initial: 0, + step: 0.1, + min: -10, + max: 10, + }, + { + type: 'number', + name: 'Spinbox', + caption: 'spinbox with step size 1', + initial: 5, + step: 1, + }, + + { + type: 'string', + name: 'String', + caption: 'Text box for string', + initial: 'hello', + }, + { + type: 'string', + name: 'String2', + caption: 'Text box for string with length 8', + initial: 'length', + maxLength: 8, + }, + + { type: 'boolean', name: 'Variable', caption: 'description', initial: true }, +] diff --git a/app/web/src/components/Svg/Svg.tsx b/app/web/src/components/Svg/Svg.tsx index f21612d..ee46676 100644 --- a/app/web/src/components/Svg/Svg.tsx +++ b/app/web/src/components/Svg/Svg.tsx @@ -27,6 +27,7 @@ type SvgNames = | 'refresh' | 'save' | 'share' + | 'switch-horizontal' | 'terminal' | 'trash' | 'x' @@ -503,6 +504,21 @@ const Svg = ({ /> ), + 'switch-horizontal': ( + + + + ), terminal: ( Promise + render: (arg: RenderArgs) => Promise } diff --git a/app/web/src/helpers/cadPackages/jsCad/jsCadController.ts b/app/web/src/helpers/cadPackages/jsCad/jsCadController.ts index 0532f53..62a92da 100644 --- a/app/web/src/helpers/cadPackages/jsCad/jsCadController.ts +++ b/app/web/src/helpers/cadPackages/jsCad/jsCadController.ts @@ -1,6 +1,7 @@ import { RenderArgs, DefaultKernelExport, + RenderResponse, createUnhealthyResponse, createHealthyResponse, } from '../common' @@ -14,6 +15,7 @@ import { Color, Mesh, } from 'three' +import { jsCadToCadhubParams } from './jscadParams' const materials = { mesh: { @@ -70,15 +72,46 @@ function CSG2Object3D(obj) { } let scriptWorker -let currentParameters = {} -const scriptUrl = '/demo-worker.js' -let resolveReference = null -let response = null -const callResolve = () => { - if (resolveReference) resolveReference() - resolveReference +type ResolveFn = (RenderResponse) => void + +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 => { + const response: Promise = 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 ({ code, @@ -86,10 +119,8 @@ export const render: DefaultKernelExport['render'] = async ({ settings, }: RenderArgs) => { if (!scriptWorker) { - console.trace( - '************************** creating new worker ************************' - ) const baseURI = document.baseURI.toString() + const scriptUrl = '/demo-worker.js' const script = `let baseURI = '${baseURI}' importScripts(new URL('${scriptUrl}',baseURI)) let worker = jscadWorker({ @@ -103,65 +134,31 @@ export const render: DefaultKernelExport['render'] = async ({ 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 + scriptWorker.addEventListener('message', ({ data }) => { if (data.action == 'parameterDefinitions') { parameterDefinitions = data.data } else if (data.action == 'entities') { if (data.error) { - response = createUnhealthyResponse(new Date(), data.error) + workerHelper.resolver(createUnhealthyResponse(new Date(), data.error)) } else { - response = createHealthyResponse({ - type: 'geometry', - data: [...data.entities.map(CSG2Object3D).filter((o) => o)], - consoleMessage: data.scriptStats, - date: new Date(), - customizerParams: parameterDefinitions, - currentParameters, - }) + workerHelper.resolver( + createHealthyResponse({ + type: 'geometry', + data: data.entities.map(CSG2Object3D).filter((o) => o), + consoleMessage: data.scriptStats, + date: new Date(), + customizerParams: jsCadToCadhubParams(parameterDefinitions || []), + }) + ) } - callResolve() } }) - callResolve() - response = null + workerHelper.resolver() scriptWorker.postMessage({ action: 'init', baseURI, alias: [] }) } - 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 - }) - - await waitResult - resolveReference = null - if (parameters) delete response.customizerParams - return response + return workerHelper.render(code, parameters) } const jsCadController: DefaultKernelExport = { diff --git a/app/web/src/helpers/cadPackages/jsCad/jscadParams.ts b/app/web/src/helpers/cadPackages/jsCad/jscadParams.ts index 1e73810..df8eb04 100644 --- a/app/web/src/helpers/cadPackages/jsCad/jscadParams.ts +++ b/app/web/src/helpers/cadPackages/jsCad/jscadParams.ts @@ -1,216 +1,130 @@ -import type { RawCustomizerParams } from '../common' +import { CadhubParams } from 'src/components/Customizer/customizerConverter' -const GROUP_SELECTOR = 'DIV[type="group"]' -const INPUT_SELECTOR = 'INPUT, SELECT' +type JscadTypeNames = + | 'group' + | 'text' + | 'int' + | 'number' + | 'slider' + | 'email' + | 'password' + | 'date' + | 'url' + | 'checkbox' + | 'color' + | 'choice' + | 'radio' -function forEachInput( - target: HTMLElement, - callback: (e: HTMLInputElement) => void -) { - target.querySelectorAll(INPUT_SELECTOR).forEach(callback) +interface JscadParamBase { + type: JscadTypeNames + caption: string + name: string +} +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) { - target.querySelectorAll(GROUP_SELECTOR).forEach(callback) -} +type JsCadParams = + | JscadGroupParam + | JscadTextParam + | JscadIntNumberSliderParam + | JscadDateParam + | JscadEmailPasswordColorParam + | JscadUrlParam + | JscadCheckboxParam + | JscadChoiceRadioParam -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 (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 - - 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 += '
    ' - buttons.forEach((button) => { - const { id, name } = - typeof button === 'string' ? { id: button, name: button } : button - html += `` - }) - 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') +export function jsCadToCadhubParams(input: JsCadParams[]): CadhubParams[] { + return input + .map((param): CadhubParams => { + switch (param.type) { + case 'slider': + case 'number': + case 'int': + return { + type: 'number', + caption: param.caption, + name: param.name, + initial: param.initial, + min: param.min, + max: param.max, + step: param.step, + decimal: param.step % 1 === 0 && param.initial % 1 === 0 ? 0 : 2, + } + case 'text': + case 'url': + case 'email': + case 'password': + case 'color': + case 'date': + return { + type: 'string', + caption: param.caption, + name: param.name, + initial: param.initial, + placeholder: + param.type === 'text' || + param.type === 'date' || + param.type === 'url' + ? param.placeholder + : '', + maxLength: + param.type === 'text' || param.type === 'url' + ? param.maxLength + : undefined, + } + case 'checkbox': + return { + type: 'boolean', + caption: param.caption, + name: param.name, + initial: !!param.initial, + } + } }) - 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)) - } 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 + .filter((a) => a) } diff --git a/app/web/src/helpers/hooks/useIdeState.ts b/app/web/src/helpers/hooks/useIdeState.ts index 1183b3b..4043182 100644 --- a/app/web/src/helpers/hooks/useIdeState.ts +++ b/app/web/src/helpers/hooks/useIdeState.ts @@ -2,6 +2,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' +import { CadhubParams } from 'src/components/Customizer/customizerConverter' function withThunk(dispatch, getState) { return (actionOrThunk) => @@ -69,14 +70,14 @@ const main = ({length=200}) => { return [transpCube, star2D, line2D, ...logo] } -const getParameterDefinitions = ()=>{ +const getParameterDefinitions = () => { return [ {type:'slider', name:'length', initial:200, caption:'Length', min:100, max:500}, { 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: '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: 'url', type: 'url', initial: 'www.example.com', size: 40, maxLength: 40, caption: 'Url:', placeholder: '40 characters' }, { name: 'password', type: 'password', initial: '', caption: 'Password:' }, @@ -116,7 +117,7 @@ export interface State { data: any quality: 'low' | 'high' } - customizerParams?: any[] + customizerParams: CadhubParams[] currentParameters?: RawCustomizerParams layout: any camera: { @@ -152,6 +153,7 @@ export const initialState: State = { data: null, quality: 'low', }, + customizerParams: [], layout: initialLayout, camera: {}, viewerSize: { width: 0, height: 0 }, @@ -175,11 +177,17 @@ 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 + const customizerParams: CadhubParams[] = payload?.customizerParams + ?.length + ? payload.customizerParams + : state.customizerParams + const currentParameters = {} + customizerParams.forEach((param) => { + currentParameters[param.name] = + typeof state?.currentParameters?.[param.name] !== 'undefined' + ? state?.currentParameters?.[param.name] + : param.initial + }) return { ...state, objectData: { @@ -187,7 +195,7 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => { type: payload.objectData?.type, data: payload.objectData?.data, }, - customizerParams: payload.customizerParams || state.customizerParams, + customizerParams, currentParameters, consoleMessages: payload.message ? [...state.consoleMessages, payload.message] @@ -203,7 +211,7 @@ export const useIdeState = (): [State, (actionOrThunk: any) => any] => { isLoading: false, } case 'setCurrentCustomizerParams': - if (!Object.keys(payload).length) return state + if (!Object.keys(payload || {}).length) return state return { ...state, currentParameters: payload, diff --git a/app/web/src/index.css b/app/web/src/index.css index 7d3b8c9..dcffa2f 100644 --- a/app/web/src/index.css +++ b/app/web/src/index.css @@ -114,103 +114,3 @@ label { 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