import React, {createContext} from "react"; import type {UiConfigRendererBase} from 'uiconfig.js' import {UiObjectConfig} from 'uiconfig.js' import {getOrCall, ValOrFunc} from 'ts-browser-helpers' import {THREE} from "../threejs"; export type BPComponentProps = { config: UiObjectConfig, level?: number disabled?: ValOrFunc // todo add to uiconfig-react as well } export type BPComponentState = { hidden?: boolean disabled?: boolean readOnly?: boolean // same as disabled(for most inputs) baseWidth?: string // for flexBasis, can be a number or a string like '50%' or '100px' } export interface UiConfigRendererBaseBp extends UiConfigRendererBase{ // eslint-disable-next-line @typescript-eslint/naming-convention THREE: THREE|undefined } export const UiConfigRendererContext = createContext(null as any) export type UiConfigRendererContextType = React.ContextType export abstract class BPComponent = BPComponentProps> extends React.Component { static contextType = UiConfigRendererContext declare context: UiConfigRendererContextType protected constructor(props: TProps, context: UiConfigRendererContextType, state: TState) { super(props, context); this.state = this.getUpdatedState(state) // this.state = state this.setState = this.setState.bind(this) } /** * Set the state(and rerender), then return a promise that resolves when the state is set * @param state */ setStatePromise(state: TState) { return new Promise(resolve => this.setState(state, resolve)) } keyVersion = 0 /** * This is called to get the updated state from the config, copy any values/properties from the config to the state here * @param state - the current state */ getUpdatedState(state: TState): TState{ // todo use UiConfigMethods.GetBaseProps const hidden = getOrCall(this.props.config.hidden) ?? false const disabled = getOrCall(this.props.disabled ?? this.props.config.disabled) ?? false const readOnly = getOrCall(this.props.config.readOnly) ?? false const baseWidth = getOrCall(this.props.config.baseWidth) ?? undefined if(hidden !== state.hidden || disabled !== state.disabled || readOnly !== state.readOnly ) this.keyVersion++ return { ...state, hidden, disabled, readOnly, baseWidth } } private _refreshing = false; /** * Refreshes the state from the config * * This is called by the ui component when the value is changed and from uiRefresh from the config * @param state - optional state to refresh from, if not provided, the current state is used */ async refreshConfigState(state?: TState) { if (!this.props.config) return; if (this._refreshing) return; this._refreshing = true const s = this.getUpdatedState(state || this.state) // console.log('set state', s) await this.setStatePromise(s) // todo debug refresh frequency etc // console.log('refreshing', this.props.config.label, this.props.config.type, this.state) this._refreshing = false } /** * Add reference to this to uiRef and set uiRefresh to the config to refresh this component * * also refreshes the config once which re-renders (todo: check if this is required) */ componentDidMount() { super.componentDidMount?.(); this.props.config.uiRef = this this.props.config.uiRefresh = (deep = false, mode = 'postFrame', delay = 0) => { this.context.addToRefreshQueue(mode, this.props.config, deep, delay) } // console.log('mount', this.props.config.label, this.props.config.type, this.props.config) this.props.config.uiRefresh() } componentWillUnmount() { // console.log('unmount', this.props.config.label, this.props.config.type, this.props.config) this.props.config.uiRef = undefined this.props.config.uiRefresh = undefined super.componentWillUnmount?.(); } // abstract updateStateValue(state: TState): Promise; }