Skip to content

Commit

Permalink
WIP feat: add fuzzy search to popup menu and search pad
Browse files Browse the repository at this point in the history
  • Loading branch information
philippfromme committed Jun 5, 2024
1 parent a248bfe commit cb6565f
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 1 deletion.
5 changes: 4 additions & 1 deletion lib/base/Modeler.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
BpmnPropertiesProviderModule as bpmnPropertiesProviderModule
} from 'bpmn-js-properties-panel';

import fuzzySearchModule from './features/fuzzy-search';

/**
* @typedef {import('bpmn-js/lib/BaseViewer').BaseViewerOptions} BaseViewerOptions
*
Expand Down Expand Up @@ -57,7 +59,8 @@ Modeler.prototype._extensionModules = [
minimapModule,
executableFixModule,
propertiesPanelModule,
bpmnPropertiesProviderModule
bpmnPropertiesProviderModule,
fuzzySearchModule
];

Modeler.prototype._modules = [].concat(
Expand Down
39 changes: 39 additions & 0 deletions lib/base/features/fuzzy-search/FuzzySearchPopupMenuProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Fuse from 'fuse.js/basic';

const options = {
includeScore: true,
ignoreLocation: true,
includeMatches: true,
threshold: 0.25,
keys: [
{
name: 'label',
weight: 3
},
{
name: 'description',
weight: 2
},
'search'
]
};

export default class FuzzySearchPopupMenuProvider {
constructor(popupMenu) {
popupMenu.registerProvider('bpmn-append', this);
popupMenu.registerProvider('bpmn-create', this);
popupMenu.registerProvider('bpmn-replace', this);
}

getPopupMenuEntries() {
return {};
}

findPopupMenuEntries(entries, pattern) {
const fuse = new Fuse(entries, options);

const result = fuse.search(pattern);

return result.map(({ item }) => item);
}
}
122 changes: 122 additions & 0 deletions lib/base/features/fuzzy-search/FuzzySearchProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import Fuse from 'fuse.js/basic';

import {
getLabel,
isLabel
} from 'bpmn-js/lib/util/LabelUtil';

const options = {
includeScore: true,
ignoreLocation: true,
includeMatches: true,
threshold: 0.25,
keys: [
{
name: 'label',
weight: 2
},
'id'
]
};

export default function BpmnSearchProvider(canvas, elementRegistry, searchPad) {
this._canvas = canvas;
this._elementRegistry = elementRegistry;

searchPad.registerProvider(this);
}

BpmnSearchProvider.$inject = [
'canvas',
'elementRegistry',
'searchPad'
];

/**
* @param {string} pattern
*
* @return {SearchResult[]}
*/
BpmnSearchProvider.prototype.find = function(pattern) {
var rootElements = this._canvas.getRootElements();

var elements = this._elementRegistry
.filter(function(element) {
return !isLabel(element) && !rootElements.includes(element);
})
.map(function(element) {
return {
element,
id: element.id,
label: getLabel(element)
};
});

const fuse = new Fuse(elements, options);

const result = fuse.search(pattern);

return result.map(({ item }) => {
const { element } = item;

return {
element,
primaryTokens: highlightSubstring(getLabel(element), pattern),
secondaryTokens: highlightSubstring(element.id, pattern)
};
});
};

function highlightSubstring(string, substring) {
if (!substring.length) return [ { normal: string } ];

const occurances = findAllSubstringOccurrences(string, substring);

if (!occurances.length) return [ { normal: string } ];

let lastIndexEnd = 0;

const tokens = [];

occurances.forEach((start, index) => {
const end = start + substring.length;

if (start !== 0) {
tokens.push({
normal: string.slice(lastIndexEnd, start)
});
}

tokens.push({
matched: string.slice(start, end)
});

if (index === occurances.length - 1 && end !== string.length - 1) {
tokens.push({
normal: string.slice(end)
});
}

lastIndexEnd = end;
});

return tokens;
}

function findAllSubstringOccurrences(string, subString) {
let indices = [];
let startIndex = 0;
let index;

while (
(index = string
.toLowerCase()
.indexOf(subString.toLowerCase(), startIndex)) > -1
) {
indices.push(index);

startIndex = index + 1;
}

return indices;
}
8 changes: 8 additions & 0 deletions lib/base/features/fuzzy-search/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import FuzzySearchProvider from './FuzzySearchProvider';
import FuzzySearchPopupMenuProvider from './FuzzySearchPopupMenuProvider';

export default {
__init__: [ 'bpmnSearch', 'fuzzySearchPopupMenuProvider' ],
bpmnSearch: [ 'type', FuzzySearchProvider ],
fuzzySearchPopupMenuProvider: [ 'type', FuzzySearchPopupMenuProvider ]
};
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"diagram-js-grid": "^1.0.0",
"diagram-js-minimap": "^5.1.0",
"diagram-js-origin": "^1.4.0",
"fuse.js": "^7.0.0",
"inherits-browser": "^0.1.0",
"min-dash": "^4.2.1",
"zeebe-bpmn-moddle": "^1.1.0"
Expand Down

0 comments on commit cb6565f

Please sign in to comment.