refactor(editor): Refactor utils files and mixins (#4654)
* ✨ Added `utils` module. Moved `canvasHelpers` and old `utils.ts` file to it * ✨ Moved rest of utils and helpers * ⚡ Fixing sytax errors * 🔨 Refactoring new utils files * 🔨 Organizing imports, adding comments and a bit more refactoring * ✔️ Fixing tests * 🔨 Moving mixins to `src`
This commit is contained in:
committed by
GitHub
parent
67983e8f94
commit
5059c57f4a
@@ -18,7 +18,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import * as CanvasHelpers from "@/views/canvasHelpers";
|
||||
import { getMidCanvasPosition } from '@/utils';
|
||||
import {DEFAULT_STICKY_HEIGHT, DEFAULT_STICKY_WIDTH, STICKY_NODE_TYPE} from "@/constants";
|
||||
import { mapStores } from "pinia";
|
||||
import { useUIStore } from "@/stores/ui";
|
||||
@@ -80,7 +80,7 @@ export default Vue.extend({
|
||||
|
||||
const offset: [number, number] = [...(this.uiStore.nodeViewOffsetPosition)];
|
||||
|
||||
const position = CanvasHelpers.getMidCanvasPosition(this.nodeViewScale, offset);
|
||||
const position = getMidCanvasPosition(this.nodeViewScale, offset);
|
||||
position[0] -= DEFAULT_STICKY_WIDTH / 2;
|
||||
position[1] -= DEFAULT_STICKY_HEIGHT / 2;
|
||||
|
||||
|
||||
@@ -94,8 +94,8 @@
|
||||
import Vue, { PropType } from 'vue';
|
||||
import camelcase from 'lodash.camelcase';
|
||||
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { globalLinkActions } from '@/components/mixins/globalLinkActions';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import { globalLinkActions } from '@/mixins/globalLinkActions';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import ItemIterator from './ItemIterator.vue';
|
||||
@@ -103,10 +103,8 @@ import NoResults from './NoResults.vue';
|
||||
import SearchBar from './SearchBar.vue';
|
||||
import { INodeCreateElement, INodeItemProps, ISubcategoryItemProps, ICategoriesWithNodes, ICategoryItemProps, INodeFilterType } from '@/Interface';
|
||||
import { WEBHOOK_NODE_TYPE, HTTP_REQUEST_NODE_TYPE, ALL_NODE_FILTER, TRIGGER_NODE_FILTER, REGULAR_NODE_FILTER, NODE_TYPE_COUNT_MAPPER } from '@/constants';
|
||||
import { matchesNodeType, matchesSelectType } from './helpers';
|
||||
import { BaseTextKey } from '@/plugins/i18n';
|
||||
import { intersection } from '@/utils';
|
||||
import { sublimeSearch } from './sortUtils';
|
||||
import { intersection, sublimeSearch, matchesNodeType, matchesSelectType } from '@/utils';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useWorkflowsStore } from '@/stores/workflows';
|
||||
import { useRootStore } from '@/stores/n8nRootStore';
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import TriggerHelperPanel from './TriggerHelperPanel.vue';
|
||||
import { ALL_NODE_FILTER, TRIGGER_NODE_FILTER, OTHER_TRIGGER_NODES_SUBCATEGORY, CORE_NODES_CATEGORY } from '@/constants';
|
||||
|
||||
@@ -58,12 +58,11 @@
|
||||
import Vue, { PropType } from 'vue';
|
||||
import { INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
import { getNewNodePosition, NODE_SIZE } from '@/views/canvasHelpers';
|
||||
import { isCommunityPackageName, getNewNodePosition, NODE_SIZE } from '@/utils';
|
||||
import { COMMUNITY_NODES_INSTALLATION_DOCS_URL } from '@/constants';
|
||||
|
||||
import NodeIcon from '@/components/NodeIcon.vue';
|
||||
import TriggerIcon from '@/components/TriggerIcon.vue';
|
||||
import { isCommunityPackageName } from '@/components/helpers';
|
||||
|
||||
Vue.component('node-icon', NodeIcon);
|
||||
Vue.component('trigger-icon', TriggerIcon);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
import Vue, { PropType } from 'vue';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
|
||||
export default mixins(externalHooks).extend({
|
||||
name: "SearchBar",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
import { PropType } from 'vue';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { externalHooks } from '@/mixins/externalHooks';
|
||||
import { INodeCreateElement } from '@/Interface';
|
||||
import { CORE_NODES_CATEGORY, WEBHOOK_NODE_TYPE, OTHER_TRIGGER_NODES_SUBCATEGORY, EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE, COMMUNICATION_CATEGORY, SCHEDULE_TRIGGER_NODE_TYPE } from '@/constants';
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { REGULAR_NODE_FILTER, TRIGGER_NODE_FILTER, ALL_NODE_FILTER } from '@/constants';
|
||||
import { INodeCreateElement, INodeItemProps } from '@/Interface';
|
||||
import { INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
|
||||
export const matchesSelectType = (el: INodeCreateElement, selectedType: string) => {
|
||||
if (selectedType === REGULAR_NODE_FILTER && el.includedByRegular) {
|
||||
return true;
|
||||
}
|
||||
if (selectedType === TRIGGER_NODE_FILTER && el.includedByTrigger) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return selectedType === ALL_NODE_FILTER;
|
||||
};
|
||||
|
||||
const matchesAlias = (nodeType: INodeTypeDescription, filter: string): boolean => {
|
||||
if (!nodeType.codex || !nodeType.codex.alias) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return nodeType.codex.alias.reduce((accu: boolean, alias: string) => {
|
||||
return accu || alias.toLowerCase().indexOf(filter) > -1;
|
||||
}, false);
|
||||
};
|
||||
|
||||
export const matchesNodeType = (el: INodeCreateElement, filter: string) => {
|
||||
const nodeType = (el.properties as INodeItemProps).nodeType;
|
||||
|
||||
return nodeType.displayName.toLowerCase().indexOf(filter) !== -1 || matchesAlias(nodeType, filter);
|
||||
};
|
||||
@@ -1,268 +0,0 @@
|
||||
// based on https://github.com/forrestthewoods/lib_fts/blob/master/code/fts_fuzzy_match.js
|
||||
|
||||
const SEQUENTIAL_BONUS = 30; // bonus for adjacent matches
|
||||
const SEPARATOR_BONUS = 30; // bonus if match occurs after a separator
|
||||
const CAMEL_BONUS = 30; // bonus if match is uppercase and prev is lower
|
||||
const FIRST_LETTER_BONUS = 15; // bonus if the first letter is matched
|
||||
|
||||
const LEADING_LETTER_PENALTY = -15; // penalty applied for every letter in str before the first match
|
||||
const MAX_LEADING_LETTER_PENALTY = -200; // maximum penalty for leading letters
|
||||
const UNMATCHED_LETTER_PENALTY = -5;
|
||||
|
||||
/**
|
||||
* Returns true if each character in pattern is found sequentially within target
|
||||
* @param {*} pattern string
|
||||
* @param {*} target string
|
||||
*/
|
||||
function fuzzyMatchSimple(pattern: string, target: string): boolean {
|
||||
let patternIdx = 0;
|
||||
let strIdx = 0;
|
||||
|
||||
while (patternIdx < pattern.length && strIdx < target.length) {
|
||||
const patternChar = pattern.charAt(patternIdx).toLowerCase();
|
||||
const targetChar = target.charAt(strIdx).toLowerCase();
|
||||
if (patternChar === targetChar) {
|
||||
patternIdx++;
|
||||
}
|
||||
++strIdx;
|
||||
}
|
||||
|
||||
return pattern.length !== 0 && target.length !== 0 && patternIdx === pattern.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a fuzzy search to find pattern inside a string.
|
||||
* @param {*} pattern string pattern to search for
|
||||
* @param {*} target string string which is being searched
|
||||
* @returns [boolean, number] a boolean which tells if pattern was
|
||||
* found or not and a search score
|
||||
*/
|
||||
function fuzzyMatch(pattern: string, target: string): {matched: boolean, outScore: number} {
|
||||
const recursionCount = 0;
|
||||
const recursionLimit = 5;
|
||||
const matches: number[] = [];
|
||||
const maxMatches = 256;
|
||||
|
||||
return fuzzyMatchRecursive(
|
||||
pattern,
|
||||
target,
|
||||
0 /* patternCurIndex */,
|
||||
0 /* strCurrIndex */,
|
||||
null /* srcMatces */,
|
||||
matches,
|
||||
maxMatches,
|
||||
0 /* nextMatch */,
|
||||
recursionCount,
|
||||
recursionLimit,
|
||||
);
|
||||
}
|
||||
|
||||
function fuzzyMatchRecursive(
|
||||
pattern: string,
|
||||
target: string,
|
||||
patternCurIndex: number,
|
||||
targetCurrIndex: number,
|
||||
targetMatches: null | number[],
|
||||
matches: number[],
|
||||
maxMatches: number,
|
||||
nextMatch: number,
|
||||
recursionCount: number,
|
||||
recursionLimit: number,
|
||||
): {matched: boolean, outScore: number} {
|
||||
let outScore = 0;
|
||||
|
||||
// Return if recursion limit is reached.
|
||||
if (++recursionCount >= recursionLimit) {
|
||||
return {matched: false, outScore};
|
||||
}
|
||||
|
||||
// Return if we reached ends of strings.
|
||||
if (patternCurIndex === pattern.length || targetCurrIndex === target.length) {
|
||||
return {matched: false, outScore};
|
||||
}
|
||||
|
||||
// Recursion params
|
||||
let recursiveMatch = false;
|
||||
let bestRecursiveMatches: number[] = [];
|
||||
let bestRecursiveScore = 0;
|
||||
|
||||
// Loop through pattern and str looking for a match.
|
||||
let firstMatch = true;
|
||||
while (patternCurIndex < pattern.length && targetCurrIndex < target.length) {
|
||||
// Match found.
|
||||
if (
|
||||
pattern[patternCurIndex].toLowerCase() === target[targetCurrIndex].toLowerCase()
|
||||
) {
|
||||
if (nextMatch >= maxMatches) {
|
||||
return {matched: false, outScore};
|
||||
}
|
||||
|
||||
if (firstMatch && targetMatches) {
|
||||
matches = [...targetMatches];
|
||||
firstMatch = false;
|
||||
}
|
||||
|
||||
const recursiveMatches: number[] = [];
|
||||
const recursiveResult = fuzzyMatchRecursive(
|
||||
pattern,
|
||||
target,
|
||||
patternCurIndex,
|
||||
targetCurrIndex + 1,
|
||||
matches,
|
||||
recursiveMatches,
|
||||
maxMatches,
|
||||
nextMatch,
|
||||
recursionCount,
|
||||
recursionLimit,
|
||||
);
|
||||
|
||||
const recursiveScore = recursiveResult.outScore;
|
||||
if (recursiveResult.matched) {
|
||||
// Pick best recursive score.
|
||||
if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
|
||||
bestRecursiveMatches = [...recursiveMatches];
|
||||
bestRecursiveScore = recursiveScore;
|
||||
}
|
||||
recursiveMatch = true;
|
||||
}
|
||||
|
||||
matches[nextMatch++] = targetCurrIndex;
|
||||
++patternCurIndex;
|
||||
}
|
||||
++targetCurrIndex;
|
||||
}
|
||||
|
||||
const matched = patternCurIndex === pattern.length;
|
||||
|
||||
if (matched) {
|
||||
outScore = 100;
|
||||
|
||||
// Apply leading letter penalty
|
||||
let penalty = LEADING_LETTER_PENALTY * matches[0];
|
||||
penalty =
|
||||
penalty < MAX_LEADING_LETTER_PENALTY
|
||||
? MAX_LEADING_LETTER_PENALTY
|
||||
: penalty;
|
||||
outScore += penalty;
|
||||
|
||||
//Apply unmatched penalty
|
||||
const unmatched = target.length - nextMatch;
|
||||
outScore += UNMATCHED_LETTER_PENALTY * unmatched;
|
||||
|
||||
// Apply ordering bonuses
|
||||
for (let i = 0; i < nextMatch; i++) {
|
||||
const currIdx = matches[i];
|
||||
|
||||
if (i > 0) {
|
||||
const prevIdx = matches[i - 1];
|
||||
if (currIdx === prevIdx + 1) {
|
||||
outScore += SEQUENTIAL_BONUS;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for bonuses based on neighbor character value.
|
||||
if (currIdx > 0) {
|
||||
// Camel case
|
||||
const neighbor = target[currIdx - 1];
|
||||
const curr = target[currIdx];
|
||||
if (
|
||||
neighbor !== neighbor.toUpperCase() &&
|
||||
curr !== curr.toLowerCase()
|
||||
) {
|
||||
outScore += CAMEL_BONUS;
|
||||
}
|
||||
const isNeighbourSeparator = neighbor === "_" || neighbor === " ";
|
||||
if (isNeighbourSeparator) {
|
||||
outScore += SEPARATOR_BONUS;
|
||||
}
|
||||
} else {
|
||||
// First letter
|
||||
outScore += FIRST_LETTER_BONUS;
|
||||
}
|
||||
}
|
||||
|
||||
// Return best result
|
||||
if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
|
||||
// Recursive score is better than "this"
|
||||
matches = [...bestRecursiveMatches];
|
||||
outScore = bestRecursiveScore;
|
||||
return {matched: true, outScore};
|
||||
} else if (matched) {
|
||||
// "this" score is better than recursive
|
||||
return {matched: true, outScore};
|
||||
} else {
|
||||
return {matched: false, outScore};
|
||||
}
|
||||
}
|
||||
return {matched: false, outScore};
|
||||
}
|
||||
|
||||
// prop = 'key'
|
||||
// prop = 'key1.key2'
|
||||
// prop = ['key1', 'key2']
|
||||
function getValue<T extends object>(obj: T, prop: string): unknown {
|
||||
if (obj.hasOwnProperty(prop)) {
|
||||
return obj[prop as keyof T];
|
||||
}
|
||||
|
||||
const segments = prop.split('.');
|
||||
let result: any = obj; // tslint:disable-line:no-any
|
||||
let i = 0;
|
||||
while (result && i < segments.length) {
|
||||
result = result[segments[i]];
|
||||
i++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function sublimeSearch<T extends object>(filter: string, data: Readonly<T[]>, keys: Array<{key: string, weight: number}>): Array<{score: number, item: T}> {
|
||||
const results = data.reduce((accu: Array<{score: number, item: T}>, item: T) => {
|
||||
let values: Array<{value: string, weight: number}> = [];
|
||||
keys.forEach(({key, weight}) => {
|
||||
const value = getValue(item, key);
|
||||
if (Array.isArray(value)) {
|
||||
values = values.concat(value.map((v) => ({value: v, weight})));
|
||||
}
|
||||
else if (typeof value === 'string') {
|
||||
values.push({
|
||||
value,
|
||||
weight,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// for each item, check every key and get maximum score
|
||||
const itemMatch = values.reduce((accu: null | {matched: boolean, outScore: number}, {value, weight}: {value: string, weight: number}) => {
|
||||
if (!fuzzyMatchSimple(filter, value)) {
|
||||
return accu;
|
||||
}
|
||||
|
||||
const match = fuzzyMatch(filter, value);
|
||||
match.outScore *= weight;
|
||||
|
||||
const {matched, outScore} = match;
|
||||
if (!accu && matched) {
|
||||
return match;
|
||||
}
|
||||
if (matched && accu && outScore > accu.outScore) {
|
||||
return match;
|
||||
}
|
||||
return accu;
|
||||
}, null);
|
||||
|
||||
if (itemMatch) {
|
||||
accu.push({
|
||||
score: itemMatch.outScore,
|
||||
item,
|
||||
});
|
||||
}
|
||||
|
||||
return accu;
|
||||
}, []);
|
||||
|
||||
results.sort((a, b) => {
|
||||
return b.score - a.score;
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
Reference in New Issue
Block a user