Skip to content

Commit

Permalink
support type to search select
Browse files Browse the repository at this point in the history
  • Loading branch information
patricklx committed Jun 1, 2023
1 parent ee92a83 commit 0e113bd
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 12 deletions.
11 changes: 6 additions & 5 deletions app/components/component-tree-item.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{{!-- template-lint-disable no-invalid-interactive --}}
<div
style={{@item.style}}
data-test-id={{@item.instance}}
class="
component-tree-item relative flex items-center mx-1 rounded
{{if @item.hasInstance "cursor-pointer" "cursor-default"}}
Expand Down Expand Up @@ -40,7 +41,7 @@
{{#if @item.isComponent}}
{{#if @item.isCurlyInvocation}}
<span class="component-name">
{{@item.name}}
{{mark-match @item.name @searchTerm}}
</span>

{{#each @item.args.positional as |value|}}
Expand All @@ -63,7 +64,7 @@
{{/each-in}}
{{else}}
<span class="component-name">
{{classify @item.name}}
{{mark-match (classify @item.name) @searchTerm}}
</span>

{{#each-in @item.args.named as |name value|}}
Expand All @@ -78,11 +79,11 @@
{{/each-in}}
{{/if}}
{{else if @item.isOutlet}}
\{{outlet "{{@item.name}}"}}
\{{outlet "{{mark-match @item.name @searchTerm}}"}}
{{else if @item.isEngine}}
\{{mount "{{@item.name}}"}}
\{{mount "{{mark-match @item.name @searchTerm}}"}}
{{else if @item.isRouteTemplate}}
{{@item.name}} route
{{mark-match @item.name @searchTerm}} route
{{/if}}
</code>
</div>
Expand Down
63 changes: 60 additions & 3 deletions app/controllers/component-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,26 @@ export default class ComponentTreeController extends Controller {

@tracked query = '';
@tracked isInspecting = false;
/**
*
* @type {RenderItem[]}
*/
@tracked renderItems = [];
@tracked _pinned = undefined;
@tracked _previewing = undefined;
@tracked searchSelect = undefined;
_store = Object.create(null);
set renderTree(renderTree) {
let { _store } = this;
let store = Object.create(null);
/**
*
* @type {RenderItem[]}
*/
let renderItems = [];
let flatten = (parent, renderNode) => {
Expand Down Expand Up @@ -80,6 +89,14 @@ export default class ComponentTreeController extends Controller {
}
get nextItem() {
if (this.searchSelect) {
const items = this.matchingItems;
const index = items.indexOf(this.findItem(this.pinned)) + 1;
return (
items.slice(index).find((i) => searchMatch(i.name, this.searchSelect)) ||
this.currentItem
);
}
const items = this.visibleItems;
return (
items[items.indexOf(this.findItem(this.pinned)) + 1] ||
Expand All @@ -88,6 +105,16 @@ export default class ComponentTreeController extends Controller {
}
get previousItem() {
if (this.searchSelect) {
const items = this.matchingItems;
const index = items.indexOf(this.findItem(this.pinned));
return (
items
.slice(0, index)
.reverse()
.find((i) => searchMatch(i.name, this.searchSelect)) || this.currentItem
);
}
const items = this.visibleItems;
return items[items.indexOf(this.findItem(this.pinned)) - 1] || items[0];
}
Expand Down Expand Up @@ -193,6 +220,10 @@ export default class ComponentTreeController extends Controller {
}
}
@action handleClick() {
this.searchSelect = false;
}
@action handleKeyDown(event) {
if (focusedInInput()) {
return;
Expand All @@ -202,25 +233,33 @@ export default class ComponentTreeController extends Controller {
event.preventDefault();
}
console.log('this._searching', this.searchSelect);
switch (event.keyCode) {
case KEYS.up:
this.pinned = this.previousItem.id;
this.pinned = this.previousItem?.id;
break;
case KEYS.right: {
if (this.searchSelect) {
break;
}
const pinnedItem = this.findItem(this.pinned);
if (pinnedItem.isExpanded) {
this.pinned = this.nextItem.id;
this.pinned = this.nextItem?.id;
} else {
pinnedItem.expand();
}
break;
}
case KEYS.down:
this.pinned = this.nextItem.id;
this.pinned = this.nextItem?.id;
break;
case KEYS.left: {
if (this.searchSelect) {
break;
}
const pinnedItem = this.findItem(this.pinned);
if (pinnedItem.isExpanded) {
Expand All @@ -231,6 +270,22 @@ export default class ComponentTreeController extends Controller {
break;
}
case KEYS.escape:
this.searchSelect = undefined;
break;
case KEYS.backspace:
this.searchSelect = (this.searchSelect || '').slice(0, -1);
break;
default:
if (event.key.length === 1) {
this.searchSelect = (this.searchSelect || '') + event.key;
if (
!this.currentItem ||
!searchMatch(this.currentItem.name, this.searchSelect)
) {
this.pinned = this.nextItem?.id;
}
}
}
}
Expand All @@ -248,10 +303,12 @@ export default class ComponentTreeController extends Controller {
@action arrowKeysSetup() {
document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('click', this.handleClick);
}
@action arrowKeysTeardown() {
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('click', this.handleClick);
}
}
Expand Down
33 changes: 33 additions & 0 deletions app/helpers/mark-match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { helper } from '@ember/component/helper';
import { htmlSafe } from '@ember/template';
import searchMatch from 'ember-inspector/utils/search-match';


function replaceRange(s, start, end, substitute) {
return s.substring(0, start) + substitute + s.substring(end);
}
/**
*
* @param str {string}
* @param regex {RegExp}
* @return {SafeString}
*/
export function markMatch([str, regex]) {
if (!regex) {
return str;
}
const match = searchMatch(str, regex);
if (!match) {
return str;
}
const matchedText = str.slice(match.index, match.index + match[0].length);
str = replaceRange(
str,
match.index,
match.index + match[0].length,
`<mark>${matchedText}</mark>`
);
return htmlSafe(str);
}

export default helper(markMatch);
11 changes: 11 additions & 0 deletions app/styles/component_tree.scss
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,14 @@
.component-tree-item--component {
color: var(--base09);
}

.component-tree-search {
background-color: var(--base00);
border: 1px solid var(--base10);
box-shadow: 0 0 3px var(--base15);
padding: 3px;
position: absolute;
right: 20px;
top: 3px;
z-index: 1;
}
6 changes: 5 additions & 1 deletion app/templates/component-tree.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
{{/in-element}}
{{/if}}

{{#if this.searchSelect}}
<div class='component-tree-search'> selecting: <strong>{{this.searchSelect}}</strong></div>
{{/if}}

<ScrollContainer
@collection={{this.visibleItems}}
@currentItem={{this.currentItem}}
Expand All @@ -24,6 +28,6 @@
@estimateHeight={{this.itemHeight}}
@items={{this.visibleItems}}
@key="id" as |item|>
<ComponentTreeItem @item={{item}} />
<ComponentTreeItem @item={{item}} @searchTerm={{this.searchSelect}}/>
</VerticalCollection>
</ScrollContainer>
1 change: 1 addition & 0 deletions app/utils/key-codes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const KEYS = {
right: 39,
down: 40,
left: 37,
backspace: 8,
};

export { KEYS };
2 changes: 1 addition & 1 deletion app/utils/search-match.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ export default function (text, searchQuery) {
return true;
}
let regExp = new RegExp(escapeRegExp(sanitize(searchQuery)));
return !!sanitize(text).match(regExp);
return sanitize(text).match(regExp);
}
96 changes: 94 additions & 2 deletions tests/acceptance/component-tree-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupTestAdapter, respondWith, sendMessage } from '../test-adapter';
import { KEYS } from 'ember-inspector/utils/key-codes';

function textFor(selector, context) {
return context.querySelector(selector).textContent.trim();
Expand Down Expand Up @@ -108,8 +109,8 @@ function getRenderTree({ withChildren, withManyChildren } = {}) {
const children = [];
if (withChildren) {
children.push(
Component({ id: 5, name: 'sub-task' }),
Component({ id: 6, name: 'sub-task' })
Component({ id: 5, name: 'sub-task-' + 5 }),
Component({ id: 6, name: 'sub-task-' + 6 })
);
}
if (withManyChildren) {
Expand Down Expand Up @@ -354,6 +355,97 @@ module('Component Tab', function (hooks) {
assert.strictEqual(treeNodes.length, 4, 'expected all tree nodes');
});

test('It should search select when typing characters', async function (assert) {
assert.expect(26);
await visit('/component-tree');

respondWith('view:showInspection', false, { count: 12 });

await sendMessage({
type: 'view:renderTree',
tree: getRenderTree({ withManyChildren: true }),
});

let expanders = findAll('.component-tree-item__expand');
let expanderEl = expanders[expanders.length - 1];
await click(expanderEl);

async function triggerCharCodes(text) {
for (let t of text) {
await triggerKeyEvent(document, 'keydown', t.toUpperCase());
}
}

let treeNodes = findAll('.component-tree-item');
assert.strictEqual(treeNodes.length, 32, 'expected all tree nodes');

respondWith('view:showInspection', false);
respondWith('objectInspector:inspectById', ({ objectId }) => {
const result = findAll(
`[data-test-id=${objectId}] .component-tree-item__tag`
)[0];
// matching initial character s
assert.strictEqual(
result.textContent.trim(),
'todos route',
'should first select todos route'
);
return false;
});

await triggerCharCodes('sub-task-1');
await rerender();

treeNodes = findAll('mark');
treeNodes.forEach((node) => {
assert.strictEqual(
node.textContent.trim(),
'SubTask1',
'SubTask1 text part should be marked'
);
});
assert.strictEqual(
treeNodes.length,
10,
'expected nodes with sub-task-1 name to be marked'
);

treeNodes = findAll('.component-tree-item');
assert.strictEqual(treeNodes.length, 32, 'expected all tree nodes');

respondWith('view:showInspection', false);

let subTaskNumber = 11;
for (let i = 0; i < 10; i++) {
await triggerKeyEvent(document, 'keydown', KEYS.down);
const node = findAll(
'.component-tree-item--pinned .component-tree-item__tag'
)[0];
assert.strictEqual(
node.textContent.trim(),
'SubTask' + subTaskNumber,
'should include SubTask1'
);
subTaskNumber++;
if (subTaskNumber === 20) {
subTaskNumber = 100;
}
}

await triggerKeyEvent(document, 'keydown', KEYS.up);
const node = findAll(
'.component-tree-item--pinned .component-tree-item__tag'
)[0];
assert.strictEqual(
node.textContent.trim(),
'SubTask19',
'should include SubTask1'
);

treeNodes = findAll('.component-tree-item');
assert.strictEqual(treeNodes.length, 33, 'expected all tree nodes');
});

test('It should update the view tree when the port triggers a change, preserving the expanded state of existing nodes', async function (assert) {
await visit('/component-tree');

Expand Down

0 comments on commit 0e113bd

Please sign in to comment.