UNPKG

@datalayer/core

Version:

[![Datalayer](https://assets.datalayer.tech/datalayer-25.svg)](https://datalayer.io)

173 lines (172 loc) 8.28 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /* * Copyright (c) 2023-2025 Datalayer, Inc. * Distributed under the terms of the Modified BSD License. */ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { nullTranslator } from '@jupyterlab/translation'; import { JSONExt } from '@lumino/coreutils'; import { Autocomplete, FormControl, TextInputWithTokens } from '@primer/react'; import { Box } from '@datalayer/primer-addons'; /** * Runtime Cell Variables Picker. */ export function RuntimeCellVariables(props) { const { getInputOptions, getOutputOptions, inputs, output, setInputs, setOutput, translator, } = props; const trans = useMemo(() => (translator ?? nullTranslator).load('jupyterlab'), [translator]); const inputForOutputRef = useRef(null); const [willSaveOutput, setWillSaveOutput] = useState(false); const [inputLoading, setInputLoading] = useState(!!getInputOptions); const [outputsState, setOutputsState] = useState(null); const [inputsState, setInputsState] = useState(inputs.map(id => ({ id, text: id, selected: true }))); const [filterVal, setFilterVal] = useState(''); // Add inputs prop in inputsState useEffect(() => { const candidates = inputs.filter(item => !inputsState.find(localItem => localItem.text === item)); if (candidates.length) { setInputsState([ ...inputsState, ...candidates.map(text => ({ id: text, text, selected: true })), ]); } }, [inputs]); // Update inputs with selected inputsState useEffect(() => { const candidates = inputsState .filter(item => item.selected) .map(item => item.text); if (!JSONExt.deepEqual(inputs, candidates)) { setInputs(candidates); } }, [inputsState]); // Fetch the input candidates useEffect(() => { if (!inputLoading) { return; } if (getInputOptions) { getInputOptions().then(ins => { const oldItems = inputsState.map(i => i.text); setInputsState([ ...inputsState, ...ins .sort() .filter(item => !oldItems.includes(item)) .map(text => ({ id: text, text, selected: false, })), ]); setInputLoading(false); }); } else { setInputLoading(false); } }, [inputsState, getInputOptions]); // Fetch the output candidates useEffect(() => { if (getOutputOptions) { Promise.all([getOutputOptions(), getInputOptions?.()]) .then(([outs, ins]) => { setOutputsState( // Add input variables as output candidate to cover // mutation case outs .sort() .concat((ins ?? []).sort()) // Remove duplicated variables; it may happen if // the code introspection matches an input. .reduce((agg, value) => { if (!agg.includes(value)) { agg.push(value); } return agg; }, []) .map(text => ({ id: text, text, selected: false, }))); }) .catch(err => { console.error('Failed to get the cell output candidates', err); setOutputsState([]); }); } }, [getOutputOptions, getInputOptions]); const onTokenRemove = useCallback((tokenId) => { const idx = inputsState.findIndex(item => item.id === tokenId); if (idx >= 0) { inputsState.splice(idx, 1); setInputsState([...inputsState]); } }, [inputsState]); const onSelectedInputsChange = useCallback((newlySelectedItems) => { if (!Array.isArray(newlySelectedItems)) { newlySelectedItems = [newlySelectedItems]; } const selectedIds = newlySelectedItems.map(i => i.id); setInputsState([ ...inputsState.map(localItem => selectedIds.includes(localItem.id) ? Object.assign(localItem, { selected: true }) : Object.assign(localItem, { selected: false })), ]); }, [inputsState]); const onItemSelect = useCallback((item) => { const oldItem = inputsState.find(localItem => localItem.id === item.id); if (!oldItem) { setInputsState([...inputsState, item]); } else { oldItem.selected = true; setInputsState([...inputsState]); } }, [inputsState]); const handleChange = useCallback((e) => { if (e.currentTarget) { setFilterVal(e.currentTarget.value); } }, []); const onOutputChange = useCallback((e) => { if (e.currentTarget) { setOutput(e.currentTarget.value); } }, []); const handleOutputMenuOpenChange = useCallback( // There is a bug in primer Autocomplete that lead to the suggestion // not triggering the `onChange` callback. So to fix it here, we // use a pointer to the underlying input element to get the value // and set it when the autocompletion menu closes. (open) => { if (!willSaveOutput) { if (open) { setWillSaveOutput(true); } return; } if (inputForOutputRef.current && !open) { setOutput(inputForOutputRef.current.value); setWillSaveOutput(false); } }, [willSaveOutput]); return (_jsxs(Box, { as: "form", sx: { p: 3 }, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { id: "cell-input-variables", children: trans.__('Inputs') }), _jsxs(Autocomplete, { children: [_jsx(Autocomplete.Input, { as: TextInputWithTokens, tokens: inputsState.filter(item => item.selected), onTokenRemove: onTokenRemove, onChange: handleChange }), _jsx(Autocomplete.Overlay, { children: _jsx(Autocomplete.Menu, { addNewItem: filterVal && !inputsState.find(localItem => localItem.text === filterVal) ? { id: filterVal, text: trans.__("Add '%1'", filterVal), handleAddItem: item => { onItemSelect({ ...item, text: filterVal, selected: true, }); setFilterVal(''); }, } : undefined, items: inputsState, selectedItemIds: inputsState .filter(item => item.selected) .map(item => item.id), onSelectedChange: onSelectedInputsChange, "aria-labelledby": "cell-input-variables", selectionVariant: "multiple", emptyStateText: trans.__('No available variables.'), loading: inputLoading }) })] })] }), _jsxs(FormControl, { children: [_jsx(FormControl.Label, { id: "cell-output-variables", children: trans.__('Output') }), _jsxs(Autocomplete, { children: [_jsx(Autocomplete.Input, { ref: inputForOutputRef, value: output, onChange: onOutputChange }), _jsx(Autocomplete.Overlay, { children: _jsx(Autocomplete.Menu, { items: outputsState ?? [], selectedItemIds: output ? [output] : [], "aria-labelledby": "cell-output-variables", loading: getOutputOptions && !outputsState, emptyStateText: trans.__('Not among the available variables.'), onOpenChange: handleOutputMenuOpenChange }) })] })] })] })); } export default RuntimeCellVariables;