feat: widget fuzzy search (#14464)
* widget fuzzy search POC * fix threshold * Makes fuse to initialize only once * add proper dependancy to useEffect * Adds more search terms * Adds search term for other widgets * reduce distance for better search results
This commit is contained in:
parent
15559d0048
commit
bd33b76c1d
|
|
@ -4,28 +4,35 @@ import WidgetCard from "./WidgetCard";
|
|||
import { getWidgetCards } from "selectors/editorSelectors";
|
||||
import ExplorerSearch from "./Explorer/ExplorerSearch";
|
||||
import { debounce } from "lodash";
|
||||
import produce from "immer";
|
||||
import {
|
||||
createMessage,
|
||||
WIDGET_SIDEBAR_CAPTION,
|
||||
} from "@appsmith/constants/messages";
|
||||
import Fuse from "fuse.js";
|
||||
import { WidgetCardProps } from "widgets/BaseWidget";
|
||||
|
||||
function WidgetSidebar({ isActive }: { isActive: boolean }) {
|
||||
const cards = useSelector(getWidgetCards);
|
||||
const [filteredCards, setFilteredCards] = useState(cards);
|
||||
const searchInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
let fuse: Fuse<WidgetCardProps, Fuse.FuseOptions<WidgetCardProps>>;
|
||||
|
||||
useEffect(() => {
|
||||
fuse = new Fuse(cards, {
|
||||
keys: ["displayName", "searchTags"],
|
||||
threshold: 0.5,
|
||||
distance: 20,
|
||||
});
|
||||
}, [cards]);
|
||||
|
||||
const filterCards = (keyword: string) => {
|
||||
let filteredCards = cards;
|
||||
if (keyword.trim().length > 0) {
|
||||
filteredCards = produce(cards, (draft) => {
|
||||
cards.forEach((card, index) => {
|
||||
if (card.displayName.toLowerCase().indexOf(keyword) === -1) {
|
||||
delete draft[index];
|
||||
}
|
||||
});
|
||||
});
|
||||
const searchResult = fuse.search(keyword);
|
||||
setFilteredCards(searchResult as WidgetCardProps[]);
|
||||
} else {
|
||||
setFilteredCards(cards);
|
||||
}
|
||||
setFilteredCards(filteredCards);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { createSelector } from "reselect";
|
|||
|
||||
import { AppState } from "reducers";
|
||||
import { WidgetConfigReducerState } from "reducers/entityReducers/widgetConfigReducer";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { WidgetCardProps, WidgetProps } from "widgets/BaseWidget";
|
||||
import {
|
||||
CanvasWidgetsReduxState,
|
||||
FlattenedWidgetProps,
|
||||
|
|
@ -206,7 +206,7 @@ export const getWidgetCards = createSelector(
|
|||
(config) => !config.hideCard,
|
||||
);
|
||||
|
||||
const _cards = cards.map((config) => {
|
||||
const _cards: WidgetCardProps[] = cards.map((config) => {
|
||||
const {
|
||||
columns,
|
||||
detachFromLayout = false,
|
||||
|
|
@ -214,6 +214,7 @@ export const getWidgetCards = createSelector(
|
|||
iconSVG,
|
||||
key,
|
||||
rows,
|
||||
searchTags,
|
||||
type,
|
||||
} = config;
|
||||
return {
|
||||
|
|
@ -224,6 +225,7 @@ export const getWidgetCards = createSelector(
|
|||
detachFromLayout,
|
||||
displayName,
|
||||
icon: iconSVG,
|
||||
searchTags,
|
||||
};
|
||||
});
|
||||
const sortedCards = sortBy(_cards, ["displayName"]);
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export const configureWidget = (config: WidgetConfiguration) => {
|
|||
const _config = {
|
||||
...features,
|
||||
...config.defaults,
|
||||
searchTags: config.searchTags,
|
||||
type: config.type,
|
||||
hideCard: !!config.hideCard || !config.iconSVG,
|
||||
isDeprecated: !!config.isDeprecated,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const CONFIG = {
|
|||
name: "Audio Recorder",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["sound recorder", "voice recorder"],
|
||||
defaults: {
|
||||
iconColor: "white",
|
||||
isDisabled: false,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const CONFIG = {
|
|||
name: "Audio",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["mp3", "sound", "wave", "player"],
|
||||
defaults: {
|
||||
rows: 4,
|
||||
columns: 28,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const CONFIG = {
|
|||
iconSVG: IconSVG,
|
||||
needsMeta: false, // Defines if this widget adds any meta properties
|
||||
isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets
|
||||
searchTags: ["click", "submit"],
|
||||
defaults: {
|
||||
rows: 4,
|
||||
columns: 24,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export const CONFIG = {
|
|||
name: "Button",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["click", "submit"],
|
||||
defaults: {
|
||||
animateLoading: true,
|
||||
text: "Submit",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
iconSVG: IconSVG,
|
||||
needsMeta: true, // Defines if this widget adds any meta properties
|
||||
isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets
|
||||
searchTags: ["photo", "video recorder"],
|
||||
defaults: {
|
||||
widgetName: "Camera",
|
||||
rows: 33,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
name: "Chart",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["graph", "visuals", "visualisations"],
|
||||
defaults: {
|
||||
rows: 32,
|
||||
columns: 24,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
name: "Checkbox",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["boolean"],
|
||||
defaults: {
|
||||
rows: 4,
|
||||
columns: 7,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const CONFIG = {
|
|||
name: "Container",
|
||||
iconSVG: IconSVG,
|
||||
isCanvas: true,
|
||||
searchTags: ["div", "parent", "group"],
|
||||
defaults: {
|
||||
backgroundColor: "#FFFFFF",
|
||||
rows: 40,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
name: "Currency Input",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["amount", "total"],
|
||||
defaults: {
|
||||
...BaseConfig.defaults,
|
||||
widgetName: "CurrencyInput",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const CONFIG = {
|
|||
name: "DatePicker",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["calendar"],
|
||||
defaults: {
|
||||
isDisabled: false,
|
||||
datePickerType: "DATE_PICKER",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const CONFIG = {
|
|||
type: Widget.getWidgetType(),
|
||||
name: "Divider",
|
||||
iconSVG: IconSVG,
|
||||
searchTags: ["line"],
|
||||
defaults: {
|
||||
rows: 4,
|
||||
columns: 20,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const CONFIG = {
|
|||
iconSVG: IconSVG,
|
||||
needsMeta: false, // Defines if this widget adds any meta properties
|
||||
isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets
|
||||
searchTags: ["pdf"],
|
||||
defaults: {
|
||||
widgetName: "DocumentViewer",
|
||||
docUrl:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const CONFIG = {
|
|||
name: "FilePicker",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["upload"],
|
||||
defaults: {
|
||||
rows: 4,
|
||||
files: [],
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const CONFIG = {
|
|||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
isCanvas: true,
|
||||
searchTags: ["group"],
|
||||
defaults: {
|
||||
rows: 40,
|
||||
columns: 24,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const CONFIG = {
|
|||
type: Widget.getWidgetType(),
|
||||
name: "Icon Button",
|
||||
iconSVG: IconSVG,
|
||||
searchTags: ["click", "submit"],
|
||||
defaults: {
|
||||
iconName: IconNames.PLUS,
|
||||
buttonVariant: ButtonVariantTypes.PRIMARY,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const CONFIG = {
|
|||
name: "Iframe",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["embed"],
|
||||
defaults: {
|
||||
source: "https://www.example.com",
|
||||
borderOpacity: 100,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const CONFIG = {
|
|||
name: "Input",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["form", "text input", "number", "textarea"],
|
||||
defaults: {
|
||||
...BaseConfig.defaults,
|
||||
inputType: "TEXT",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
iconSVG: IconSVG,
|
||||
needsMeta: true, // Defines if this widget adds any meta properties
|
||||
isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets
|
||||
searchTags: ["graph", "visuals", "visualisations"],
|
||||
defaults: {
|
||||
rows: 32,
|
||||
columns: 24,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export const CONFIG = {
|
|||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
isCanvas: true,
|
||||
searchTags: ["dialog", "popup", "notification"],
|
||||
defaults: {
|
||||
rows: 24,
|
||||
columns: 24,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
name: "Multi TreeSelect",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["dropdown"],
|
||||
defaults: {
|
||||
rows: 4,
|
||||
columns: 20,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
name: "MultiSelect",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["dropdown", "tags"],
|
||||
defaults: {
|
||||
rows: 4,
|
||||
columns: 20,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
name: "Phone Input",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["call"],
|
||||
defaults: {
|
||||
...BaseConfig.defaults,
|
||||
widgetName: "PhoneInput",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const CONFIG = {
|
|||
iconSVG: IconSVG,
|
||||
needsMeta: false, // Defines if this widget adds any meta properties
|
||||
isCanvas: false, // Defines if this widget has a canvas within in which we can drop other widgets
|
||||
searchTags: ["percent"],
|
||||
defaults: {
|
||||
widgetName: "Progress",
|
||||
rows: 4,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
name: "Radio Group",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["choice"],
|
||||
defaults: {
|
||||
rows: 8,
|
||||
columns: 20,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const CONFIG = {
|
|||
name: "Rating",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["stars"],
|
||||
defaults: {
|
||||
rows: 4,
|
||||
columns: 10,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
name: "Rich Text Editor",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["input", "rte"],
|
||||
defaults: {
|
||||
defaultText: "This is the initial <b>content</b> of the editor",
|
||||
rows: 20,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
name: "Select",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["dropdown"],
|
||||
defaults: {
|
||||
rows: 4,
|
||||
columns: 20,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import Widget from "./widget";
|
|||
export const CONFIG = {
|
||||
type: Widget.getWidgetType(),
|
||||
name: "TreeSelect",
|
||||
searchTags: ["dropdown"],
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
defaults: {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export const CONFIG = {
|
|||
name: "Switch",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["boolean"],
|
||||
defaults: {
|
||||
label: "Label",
|
||||
rows: 4,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const CONFIG = {
|
|||
name: "Table",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["datagrid"],
|
||||
defaults: {
|
||||
rows: 28,
|
||||
columns: 34,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const CONFIG = {
|
|||
type: Widget.getWidgetType(),
|
||||
name: "Text",
|
||||
iconSVG: IconSVG,
|
||||
searchTags: ["typography", "paragraph"],
|
||||
defaults: {
|
||||
text: "Label",
|
||||
fontSize: DEFAULT_FONT_SIZE,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const CONFIG = {
|
|||
name: "Video",
|
||||
iconSVG: IconSVG,
|
||||
needsMeta: true,
|
||||
searchTags: ["youtube"],
|
||||
defaults: {
|
||||
rows: 28,
|
||||
columns: 24,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export interface WidgetConfiguration {
|
|||
isCanvas?: boolean;
|
||||
needsMeta?: boolean;
|
||||
features?: WidgetFeatures;
|
||||
searchTags?: string[];
|
||||
properties: {
|
||||
config: PropertyPaneConfig[];
|
||||
default: Record<string, string>;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user