Skip to content

Commit

Permalink
feat(dmn-js-drd): implement search
Browse files Browse the repository at this point in the history
  • Loading branch information
barmac committed Oct 13, 2023
1 parent ba631f1 commit f30c2c6
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 13 deletions.
11 changes: 7 additions & 4 deletions packages/dmn-js-drd/src/NavigatedViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import inherits from 'inherits-browser';

import Viewer from './Viewer';

import ZoomScroll from 'diagram-js/lib/navigation/zoomscroll';
import MoveCanvas from 'diagram-js/lib/navigation/movecanvas';
import TouchModule from 'diagram-js/lib/navigation/touch';

import DmnSearchModule from './features/search';

/**
* A viewer that includes mouse navigation facilities
Expand All @@ -14,14 +19,12 @@ export default function NavigatedViewer(options) {

inherits(NavigatedViewer, Viewer);

import ZoomScroll from 'diagram-js/lib/navigation/zoomscroll';
import MoveCanvas from 'diagram-js/lib/navigation/movecanvas';
import TouchModule from 'diagram-js/lib/navigation/touch';

NavigatedViewer.prototype._navigationModules = [
ZoomScroll,
MoveCanvas,
TouchModule
TouchModule,
DmnSearchModule
];

NavigatedViewer.prototype._modules = [].concat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ DrdEditorActions.prototype._registerDefaultActions = function(injector) {

// (1) retrieve optional components to integrate with

var canvas = injector.get('canvas', false);
var elementRegistry = injector.get('elementRegistry', false);
var selection = injector.get('selection', false);
var lassoTool = injector.get('lassoTool', false);
var handTool = injector.get('handTool', false);
var directEditing = injector.get('directEditing', false);
var distributeElements = injector.get('distributeElements', false);
var alignElements = injector.get('alignElements', false);
const canvas = injector.get('canvas', false),
elementRegistry = injector.get('elementRegistry', false),
selection = injector.get('selection', false),
lassoTool = injector.get('lassoTool', false),
handTool = injector.get('handTool', false),
directEditing = injector.get('directEditing', false),
distributeElements = injector.get('distributeElements', false),
alignElements = injector.get('alignElements', false),
searchPad = injector.get('searchPad', false);

// (2) check components and register actions

Expand Down Expand Up @@ -97,4 +98,10 @@ DrdEditorActions.prototype._registerDefaultActions = function(injector) {
}
});
}

if (selection && searchPad) {
this._registerAction('find', function() {
searchPad.toggle();
});
}
};
12 changes: 12 additions & 0 deletions packages/dmn-js-drd/src/features/keyboard/DrdKeyboardBindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,16 @@ DrdKeyboardBindings.prototype.registerBindings = function(keyboard, editorAction
}
});

// search labels
// CTRL + F
addListener('find', function(context) {

var event = context.keyEvent;

if (keyboard.isKey([ 'f', 'F' ], event) && keyboard.isCmd(event)) {
editorActions.trigger('find');

return true;
}
});
};
139 changes: 139 additions & 0 deletions packages/dmn-js-drd/src/features/search/DmnSearchProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {
map,
filter,
sortBy
} from 'min-dash';

import {
getLabel
} from '../label-editing/LabelUtil';

/**
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/features/search-pad/SearchPad').default} SearchPad
*
* @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').default
* } SearchPadProvider
* @typedef {import('diagram-js/lib/features/search-pad/SearchPadProvider').SearchResult
* } SearchResult
*/

/**
* Provides ability to search for DMN elements.
*
* @implements {SearchPadProvider}
*
* @param {ElementRegistry} elementRegistry
* @param {SearchPad} searchPad
* @param {Canvas} canvas
*/
export default function DmnSearchProvider(elementRegistry, searchPad, canvas) {
this._elementRegistry = elementRegistry;
this._canvas = canvas;

searchPad.registerProvider(this);
}

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

/**
* @param {string} pattern
*
* @return {SearchResult[]}
*/
DmnSearchProvider.prototype.find = function(pattern) {
const rootElement = this._canvas.getRootElement();

let elements = this._elementRegistry.filter(function(element) {
if (element.labelTarget) {
return false;
}
return true;
});

// do not include root element
elements = filter(elements, function(element) {
return element !== rootElement;
});

elements = map(elements, function(element) {
return {
primaryTokens: matchAndSplit(getLabel(element), pattern),
secondaryTokens: matchAndSplit(element.id, pattern),
element: element
};
});

// exclude non-matched elements
elements = filter(elements, function(element) {
return hasMatched(element.primaryTokens) || hasMatched(element.secondaryTokens);
});

elements = sortBy(elements, function(element) {
return getLabel(element.element) + element.element.id;
});

return elements;
};

/**
* @param {Token[]} tokens
*
* @return {boolean}
*/
function hasMatched(tokens) {
const matched = filter(tokens, function(token) {
return !!token.matched;
});

return matched.length > 0;
}

/**
* @param {string} text
* @param {string} pattern
*
* @return {Token[]}
*/
function matchAndSplit(text, pattern) {
const tokens = [],
originalText = text;

if (!text) {
return tokens;
}

text = text.toLowerCase();
pattern = pattern.toLowerCase();

const i = text.indexOf(pattern);

if (i > -1) {
if (i !== 0) {
tokens.push({
normal: originalText.substr(0, i)
});
}

tokens.push({
matched: originalText.substr(i, pattern.length)
});

if (pattern.length + i < text.length) {
tokens.push({
normal: originalText.substr(pattern.length + i, text.length)
});
}
} else {
tokens.push({
normal: originalText
});
}

return tokens;
}
12 changes: 12 additions & 0 deletions packages/dmn-js-drd/src/features/search/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import SearchPadModule from 'diagram-js/lib/features/search-pad';

import DmnSearchProvider from './DmnSearchProvider';


export default {
__depends__: [
SearchPadModule
],
__init__: [ 'dmnSearch' ],
dmnSearch: [ 'type', DmnSearchProvider ]
};
3 changes: 2 additions & 1 deletion packages/dmn-js-drd/test/spec/ModelerSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ describe('Modeler', function() {
'alignElements',
'lassoTool',
'handTool',
'directEditing'
'directEditing',
'find'
];

// when
Expand Down
143 changes: 143 additions & 0 deletions packages/dmn-js-drd/test/spec/features/search/DmnSearchProviderSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import {
bootstrapViewer,
inject
} from 'test/TestHelper';

import coreModule from 'src/core';
import modelingModule from 'src/features/modeling';
import dmnSearchModule from 'src/features/search';


describe('features - DMN search provider', function() {

const testModules = [
coreModule,
modelingModule,
dmnSearchModule
];


const diagramXML = require('./dmn-search.dmn');

beforeEach(bootstrapViewer(diagramXML, { modules: testModules }));


it('find should return all elements that match label or ID', inject(
function(dmnSearch) {

// given
const pattern = 'Decision';

// when
const elements = dmnSearch.find(pattern);

// then
expect(elements).length(2);
elements.forEach(function(e) {
expect(e).to.have.property('element');
expect(e).to.have.property('primaryTokens');
expect(e).to.have.property('secondaryTokens');
});
})
);


it('matches IDs', inject(function(dmnSearch) {

// given
const pattern = 'Decision_id';

// when
const elements = dmnSearch.find(pattern);

// then
expect(elements[0].primaryTokens).to.eql([
{ normal: 'Decision 1' }
]);
expect(elements[0].secondaryTokens).to.eql([
{ matched: 'Decision_id' },
{ normal: '_1' }
]);
}));


it('should not return root element (definitions)', inject(function(dmnSearch) {

// given
const pattern = 'Definitions';

// when
const elements = dmnSearch.find(pattern);

// then
expect(elements).to.have.length(0);
}));


describe('should split result into matched and non matched tokens', function() {

it('matched all', inject(function(dmnSearch) {

// given
const pattern = 'Start Middle End';

// when
const elements = dmnSearch.find(pattern);

// then
expect(elements[0].primaryTokens).to.eql([
{ matched: 'Start Middle End' }
]);
}));


it('matched start', inject(function(dmnSearch) {

// given
const pattern = 'Start';

// when
const elements = dmnSearch.find(pattern);

// then
expect(elements[0].primaryTokens).to.eql([
{ matched: 'Start' },
{ normal: ' Middle End' }
]);
}));


it('matched middle', inject(function(dmnSearch) {

// given
const pattern = 'Middle';

// when
const elements = dmnSearch.find(pattern);

// then
expect(elements[0].primaryTokens).to.eql([
{ normal: 'Start ' },
{ matched: 'Middle' },
{ normal: ' End' }
]);
}));


it('matched end', inject(function(dmnSearch) {

// given
const pattern = 'End';

// when
const elements = dmnSearch.find(pattern);

// then
expect(elements[0].primaryTokens).to.eql([
{ normal: 'Start Middle ' },
{ matched: 'End' }
]);
}));

});
});
Loading

0 comments on commit f30c2c6

Please sign in to comment.