Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support type to search & select #2401

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
64 changes: 61 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,16 @@ 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 +107,17 @@ 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 +223,10 @@ export default class ComponentTreeController extends Controller {
}
}

@action handleClick() {
this.searchSelect = false;
}

@action handleKeyDown(event) {
if (focusedInInput()) {
return;
Expand All @@ -204,23 +238,29 @@ export default class ComponentTreeController extends Controller {

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 +271,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 +304,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
32 changes: 32 additions & 0 deletions app/helpers/mark-match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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