/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { SqlExpression } from 'druid-query-toolkit'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useStore } from 'zustand'; import { shallow } from 'zustand/shallow'; import type { Host } from './host'; import type { ParameterDefinitions } from './parameter'; import type { ParametersToParams, VisualModuleInstance } from './visual-module'; export interface SingleModuleHostProps { moduleName: string; host: Host; } export function useSingleModuleHost(options: { host: Host; moduleName: string }) { const { host, moduleName } = options; const containerRef = useRef(null); const { setModuleParams, setModuleTable, setModuleWhere, visualModule, setModuleHaving } = useStore( host.store, useCallback( state => { const visualModule = state.visualModules ? state.visualModules[moduleName] : undefined; const { setModuleParams, setModuleTable, setModuleWhere, setModuleHaving } = state; return { visualModule, setModuleParams, setModuleTable, setModuleWhere, setModuleHaving, }; }, [moduleName], ), shallow, ); const [instance, setInstance] = useState>>(); const instanceRef = useRef(instance); const reset = useCallback(async () => { if (instance?.destroy) { await instance.destroy(); } if (!containerRef.current || !visualModule) return; // TODO: better way to clear the container? containerRef.current.innerHTML = ''; instanceRef.current = visualModule({ container: containerRef.current, host, getLastUpdateEvent: () => { return host.store.getState().getUpdateEvent(moduleName); }, updateParam(name, value) { setModuleParams(moduleName, prev => ({ ...prev, [name]: value })); }, updateParams(params) { setModuleParams(moduleName, prev => ({ ...prev, ...params } as ParameterDefinitions)); }, updateTable(table) { setModuleTable(moduleName, table); }, updateWhere(where) { setModuleWhere(moduleName, typeof where === 'string' ? SqlExpression.parse(where) : where); }, updateHaving(having) { setModuleHaving( moduleName, typeof having === 'string' ? SqlExpression.parse(having) : having, ); }, }); setInstance(instanceRef.current); // eslint-disable-next-line react-hooks/exhaustive-deps }, [visualModule]); useEffect(() => { void reset(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [visualModule]); useEffect(() => { return () => { void instanceRef.current?.destroy?.(); }; }, []); useEffect(() => { if (!instance) return; const unsubscribe = host.store.subscribe(state => { const update = state.getUpdateEvent(moduleName); const res = instance.update(update); if (res && 'catch' in res) { res.catch(e => { console.error(`Error updating visual module named "${moduleName}":`); console.error(e); }); } }); void instance.update(host.store.getState().getUpdateEvent(moduleName)); return unsubscribe; // eslint-disable-next-line react-hooks/exhaustive-deps -- exclude moduleName, host.store }, [instance]); return [containerRef]; }