|
@@ -11,16 +11,28 @@
|
|
|
* specific language governing permissions and limitations under the License.
|
|
|
*/
|
|
|
|
|
|
-import React, { useCallback, SyntheticEvent, useState, useEffect, useMemo, ComponentType, MouseEvent } from "react";
|
|
|
-import { Theme, alpha } from "@mui/material";
|
|
|
+import React, {
|
|
|
+ useCallback,
|
|
|
+ SyntheticEvent,
|
|
|
+ useState,
|
|
|
+ useEffect,
|
|
|
+ useMemo,
|
|
|
+ ComponentType,
|
|
|
+ MouseEvent,
|
|
|
+ ChangeEvent,
|
|
|
+} from "react";
|
|
|
+import { TextField, Theme, alpha } from "@mui/material";
|
|
|
import Badge, { BadgeOrigin } from "@mui/material/Badge";
|
|
|
-import Box from "@mui/material/Box";
|
|
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
|
import Grid from "@mui/material/Grid";
|
|
|
import IconButton from "@mui/material/IconButton";
|
|
|
import Switch from "@mui/material/Switch";
|
|
|
import Tooltip from "@mui/material/Tooltip";
|
|
|
-import { ChevronRight, FlagOutlined, PushPinOutlined } from "@mui/icons-material";
|
|
|
+import ChevronRight from "@mui/icons-material/ChevronRight";
|
|
|
+import FlagOutlined from "@mui/icons-material/FlagOutlined";
|
|
|
+import PushPinOutlined from "@mui/icons-material/PushPinOutlined";
|
|
|
+import SearchOffOutlined from "@mui/icons-material/SearchOffOutlined";
|
|
|
+import SearchOutlined from "@mui/icons-material/SearchOutlined";
|
|
|
import { SimpleTreeView } from "@mui/x-tree-view/SimpleTreeView";
|
|
|
import { TreeItem } from "@mui/x-tree-view/TreeItem";
|
|
|
|
|
@@ -32,9 +44,12 @@ import {
|
|
|
useDispatchRequestUpdateOnFirstRender,
|
|
|
createRequestUpdateAction,
|
|
|
useDynamicProperty,
|
|
|
+ ColumnDesc,
|
|
|
+ FilterDesc,
|
|
|
+ TableFilter,
|
|
|
} from "taipy-gui";
|
|
|
|
|
|
-import { Cycles, Cycle, DataNodes, NodeType, Scenarios, Scenario, DataNode, Sequence } from "./utils/types";
|
|
|
+import { Cycles, Cycle, DataNodes, NodeType, Scenarios, Scenario, DataNode, Sequence, Sequences } from "./utils/types";
|
|
|
import {
|
|
|
Cycle as CycleIcon,
|
|
|
Datanode as DatanodeIcon,
|
|
@@ -48,6 +63,7 @@ import {
|
|
|
EmptyArray,
|
|
|
FlagSx,
|
|
|
ParentItemSx,
|
|
|
+ getUpdateVarNames,
|
|
|
iconLabelSx,
|
|
|
tinyIconButtonSx,
|
|
|
tinySelPinIconButtonSx,
|
|
@@ -89,6 +105,9 @@ interface CoreSelectorProps {
|
|
|
editComponent?: ComponentType<EditProps>;
|
|
|
showPins?: boolean;
|
|
|
onSelect?: (id: string | string[]) => void;
|
|
|
+ updateCoreVars: string;
|
|
|
+ filter?: string;
|
|
|
+ showSearch: boolean;
|
|
|
}
|
|
|
|
|
|
const tinyPinIconButtonSx = (theme: Theme) => ({
|
|
@@ -102,7 +121,8 @@ const tinyPinIconButtonSx = (theme: Theme) => ({
|
|
|
},
|
|
|
});
|
|
|
|
|
|
-const switchBoxSx = { ml: 2 };
|
|
|
+const switchBoxSx = { ml: 2, width: (theme: Theme) => `calc(100% - ${theme.spacing(2)})` };
|
|
|
+const iconInRowSx = { fontSize: "body2.fontSize" };
|
|
|
|
|
|
const CoreItem = (props: {
|
|
|
item: Entity;
|
|
@@ -243,6 +263,33 @@ const getExpandedIds = (nodeId: string, exp?: string[], entities?: Entities) =>
|
|
|
return exp || [];
|
|
|
};
|
|
|
|
|
|
+const emptyEntity = [] as unknown as Entity;
|
|
|
+const filterTree = (entities: Entities, search: string, leafType: NodeType, count?: { nb: number }) => {
|
|
|
+ let top = false;
|
|
|
+ if (!count) {
|
|
|
+ count = { nb: 0 };
|
|
|
+ top = true;
|
|
|
+ }
|
|
|
+ const filtered = entities
|
|
|
+ .map((item) => {
|
|
|
+ const [, label, items, nodeType] = item;
|
|
|
+ if (nodeType !== leafType || label.toLowerCase().includes(search)) {
|
|
|
+ const newItem = [...item];
|
|
|
+ if (Array.isArray(items) && items.length) {
|
|
|
+ newItem[2] = filterTree(items, search, leafType, count) as Scenarios | DataNodes | Sequences;
|
|
|
+ }
|
|
|
+ return newItem as Entity;
|
|
|
+ }
|
|
|
+ count.nb++;
|
|
|
+ return emptyEntity;
|
|
|
+ })
|
|
|
+ .filter((i) => (i as unknown[]).length !== 0);
|
|
|
+ if (top && count.nb == 0) {
|
|
|
+ return entities;
|
|
|
+ }
|
|
|
+ return filtered;
|
|
|
+};
|
|
|
+
|
|
|
const CoreSelector = (props: CoreSelectorProps) => {
|
|
|
const {
|
|
|
id = "",
|
|
@@ -261,6 +308,8 @@ const CoreSelector = (props: CoreSelectorProps) => {
|
|
|
onChange,
|
|
|
onSelect,
|
|
|
coreChanged,
|
|
|
+ updateCoreVars,
|
|
|
+ showSearch,
|
|
|
} = props;
|
|
|
|
|
|
const [selectedItems, setSelectedItems] = useState<string[]>([]);
|
|
@@ -294,7 +343,11 @@ const CoreSelector = (props: CoreSelectorProps) => {
|
|
|
const res = isSelected ? [...old, nodeId] : old.filter((id) => id !== nodeId);
|
|
|
const scenariosVar = getUpdateVar(updateVars, lovPropertyName);
|
|
|
const val = multiple ? res : isSelectable ? nodeId : "";
|
|
|
- setTimeout(() => dispatch(createSendUpdateAction(updateVarName, val, module, onChange, propagate, scenariosVar)), 1);
|
|
|
+ setTimeout(
|
|
|
+ () =>
|
|
|
+ dispatch(createSendUpdateAction(updateVarName, val, module, onChange, propagate, scenariosVar)),
|
|
|
+ 1
|
|
|
+ );
|
|
|
onSelect && isSelectable && onSelect(val);
|
|
|
return res;
|
|
|
});
|
|
@@ -304,8 +357,8 @@ const CoreSelector = (props: CoreSelectorProps) => {
|
|
|
|
|
|
useEffect(() => {
|
|
|
if (value !== undefined && value !== null) {
|
|
|
- setSelectedItems(Array.isArray(value) ? value : value ? [value]: []);
|
|
|
- setExpandedItems((exp) => typeof value === "string" ? getExpandedIds(value, exp, props.entities) : exp);
|
|
|
+ setSelectedItems(Array.isArray(value) ? value : value ? [value] : []);
|
|
|
+ setExpandedItems((exp) => (typeof value === "string" ? getExpandedIds(value, exp, props.entities) : exp));
|
|
|
} else if (defaultValue) {
|
|
|
try {
|
|
|
const parsedValue = JSON.parse(defaultValue);
|
|
@@ -332,14 +385,25 @@ const CoreSelector = (props: CoreSelectorProps) => {
|
|
|
setSelectedItems((old) => {
|
|
|
if (old.length) {
|
|
|
const lovVar = getUpdateVar(updateVars, lovPropertyName);
|
|
|
- setTimeout(() => dispatch(
|
|
|
- createSendUpdateAction(updateVarName, multiple ? [] : "", module, onChange, propagate, lovVar)
|
|
|
- ), 1);
|
|
|
+ setTimeout(
|
|
|
+ () =>
|
|
|
+ dispatch(
|
|
|
+ createSendUpdateAction(
|
|
|
+ updateVarName,
|
|
|
+ multiple ? [] : "",
|
|
|
+ module,
|
|
|
+ onChange,
|
|
|
+ propagate,
|
|
|
+ lovVar
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ 1
|
|
|
+ );
|
|
|
return [];
|
|
|
}
|
|
|
return old;
|
|
|
});
|
|
|
- }
|
|
|
+ }
|
|
|
}, [entities, updateVars, lovPropertyName, updateVarName, multiple, module, onChange, propagate, dispatch]);
|
|
|
|
|
|
// Refresh on broadcast
|
|
@@ -408,22 +472,109 @@ const CoreSelector = (props: CoreSelectorProps) => {
|
|
|
[showPins, props.entities]
|
|
|
);
|
|
|
|
|
|
+ // filters
|
|
|
+ const colFilters = useMemo(() => {
|
|
|
+ try {
|
|
|
+ const res = props.filter ? (JSON.parse(props.filter) as Array<[string, string, string[]]>) : undefined;
|
|
|
+ return Array.isArray(res)
|
|
|
+ ? res.reduce((pv, [name, coltype, lov], idx) => {
|
|
|
+ pv[name] = { dfid: name, type: coltype, index: idx, filter: true, lov: lov, freeLov: !!lov };
|
|
|
+ return pv;
|
|
|
+ }, {} as Record<string, ColumnDesc>)
|
|
|
+ : undefined;
|
|
|
+ } catch (e) {
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+ }, [props.filter]);
|
|
|
+ const [filters, setFilters] = useState<FilterDesc[]>([]);
|
|
|
+
|
|
|
+ const applyFilters = useCallback(
|
|
|
+ (filters: FilterDesc[]) => {
|
|
|
+ setFilters((old) => {
|
|
|
+ if (old.length != filters.length || JSON.stringify(old) != JSON.stringify(filters)) {
|
|
|
+ const filterVar = getUpdateVar(updateCoreVars, "filter");
|
|
|
+ dispatch(
|
|
|
+ createRequestUpdateAction(
|
|
|
+ props.id,
|
|
|
+ module,
|
|
|
+ getUpdateVarNames(updateVars, lovPropertyName),
|
|
|
+ true,
|
|
|
+ filterVar ? { [filterVar]: filters } : undefined
|
|
|
+ )
|
|
|
+ );
|
|
|
+ return filters;
|
|
|
+ }
|
|
|
+ return old;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ [updateVars, dispatch, props.id, updateCoreVars, lovPropertyName, module]
|
|
|
+ );
|
|
|
+
|
|
|
+ // Search
|
|
|
+ const [searchValue, setSearchValue] = useState("");
|
|
|
+ const onSearch = useCallback((e: ChangeEvent<HTMLInputElement>) => setSearchValue(e.currentTarget.value), []);
|
|
|
+ const foundEntities = useMemo(() => {
|
|
|
+ if (!entities || searchValue === "") {
|
|
|
+ return entities;
|
|
|
+ }
|
|
|
+ return filterTree(entities, searchValue.toLowerCase(), props.leafType);
|
|
|
+ }, [entities, searchValue, props.leafType]);
|
|
|
+ const [revealSearch, setRevealSearch] = useState(false);
|
|
|
+ const onRevealSearch = useCallback(() => {
|
|
|
+ setRevealSearch((r) => !r);
|
|
|
+ setSearchValue("");
|
|
|
+ }, []);
|
|
|
+
|
|
|
return (
|
|
|
<>
|
|
|
- {showPins ? (
|
|
|
- <Box sx={switchBoxSx}>
|
|
|
- <FormControlLabel
|
|
|
- control={
|
|
|
- <Switch
|
|
|
- onChange={onShowPinsChange}
|
|
|
- checked={hideNonPinned}
|
|
|
- disabled={!hideNonPinned && !Object.keys(pins[0]).length}
|
|
|
- />
|
|
|
- }
|
|
|
- label="Pinned only"
|
|
|
- />
|
|
|
- </Box>
|
|
|
- ) : null}
|
|
|
+ <Grid container sx={switchBoxSx} gap={1}>
|
|
|
+ {active && colFilters ? (
|
|
|
+ <Grid item>
|
|
|
+ <TableFilter
|
|
|
+ columns={colFilters}
|
|
|
+ appliedFilters={filters}
|
|
|
+ filteredCount={0}
|
|
|
+ onValidate={applyFilters}
|
|
|
+ ></TableFilter>
|
|
|
+ </Grid>
|
|
|
+ ) : null}
|
|
|
+ {showPins ? (
|
|
|
+ <Grid item>
|
|
|
+ <FormControlLabel
|
|
|
+ control={
|
|
|
+ <Switch
|
|
|
+ onChange={onShowPinsChange}
|
|
|
+ checked={hideNonPinned}
|
|
|
+ disabled={!hideNonPinned && !Object.keys(pins[0]).length}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ label="Pinned only"
|
|
|
+ />
|
|
|
+ </Grid>
|
|
|
+ ) : null}
|
|
|
+ {showSearch ? (
|
|
|
+ <Grid item>
|
|
|
+ <IconButton onClick={onRevealSearch} size="small" sx={iconInRowSx}>
|
|
|
+ {revealSearch ? (
|
|
|
+ <SearchOffOutlined fontSize="inherit" />
|
|
|
+ ) : (
|
|
|
+ <SearchOutlined fontSize="inherit" />
|
|
|
+ )}
|
|
|
+ </IconButton>
|
|
|
+ </Grid>
|
|
|
+ ) : null}
|
|
|
+ {showSearch && revealSearch ? (
|
|
|
+ <Grid item xs={12}>
|
|
|
+ <TextField
|
|
|
+ margin="dense"
|
|
|
+ value={searchValue}
|
|
|
+ onChange={onSearch}
|
|
|
+ fullWidth
|
|
|
+ label="Search"
|
|
|
+ ></TextField>
|
|
|
+ </Grid>
|
|
|
+ ) : null}
|
|
|
+ </Grid>
|
|
|
<SimpleTreeView
|
|
|
slots={treeSlots}
|
|
|
sx={treeViewSx}
|
|
@@ -433,8 +584,8 @@ const CoreSelector = (props: CoreSelectorProps) => {
|
|
|
expandedItems={expandedItems}
|
|
|
onItemExpansionToggle={onItemExpand}
|
|
|
>
|
|
|
- {entities
|
|
|
- ? entities.map((item) => (
|
|
|
+ {foundEntities
|
|
|
+ ? foundEntities.map((item) => (
|
|
|
<CoreItem
|
|
|
key={item ? item[0] : ""}
|
|
|
item={item}
|