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

Improve filter interface #122

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ First release.
> This section will be removed after the beta phase. <br>
> Note that semantic versioning rules are not strictly followed during this phase.

- feature/next-match: Filter: Add support for prev/next-match
- feature/next-match: Filter: New mode 'mark' (like 'dim' but does not gray out)

- v0.12.1: Fix flat source format for positional args.

- v0.12.0: Add `deep`, `resetLazy`, and `collapseOthers` options to
Expand Down
27 changes: 25 additions & 2 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
* @VERSION, @DATE (https://github.com/mar10/wunderbaum)
*/

import { MatcherCallback, SourceListType, SourceObjectType } from "./types";
import {
ApplyCommandType,
MatcherCallback,
NavigationType,
SourceListType,
SourceObjectType,
} from "./types";
import * as util from "./util";
import { WunderbaumNode } from "./wb_node";

Expand Down Expand Up @@ -140,7 +146,24 @@ export const RESERVED_TREE_SOURCE_KEYS: Set<string> = new Set([
// ]);

/** Map `KeyEvent.key` to navigation action. */
export const KEY_TO_ACTION_DICT: { [key: string]: string } = {
export const KEY_TO_NAVIGATION_MAP: { [key: string]: NavigationType } = {
ArrowDown: "down",
ArrowLeft: "left",
ArrowRight: "right",
ArrowUp: "up",
Backspace: "parent",
End: "lastCol",
Home: "firstCol",
"Control+End": "last",
"Control+Home": "first",
"Meta+ArrowDown": "last", // macOs
"Meta+ArrowUp": "first", // macOs
PageDown: "pageDown",
PageUp: "pageUp",
};

/** Map `KeyEvent.key` to navigation action. */
export const KEY_TO_COMMAND_MAP: { [key: string]: ApplyCommandType } = {
" ": "toggleSelect",
"+": "expand",
Add: "expand",
Expand Down
56 changes: 45 additions & 11 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,29 +518,43 @@ export interface WbEventInfo {
// export type WbNodeCallbackType = (e: WbNodeEventType) => any;
// export type WbRenderCallbackType = (e: WbRenderEventType) => void;

export type FilterModeType = null | "dim" | "hide";
export type FilterModeType = null | "mark" | "dim" | "hide";
export type SelectModeType = "single" | "multi" | "hier";

export type NavigationType =
| "down"
| "first"
| "firstCol"
| "last"
| "lastCol"
| "left"
| "nextMatch"
| "pageDown"
| "pageUp"
| "parent"
| "prevMatch"
| "right"
| "up";

export type ApplyCommandType =
| NavigationType
| "addChild"
| "addSibling"
| "collapse"
| "collapseAll"
| "copy"
| "cut"
| "down"
| "first"
| "edit"
| "expand"
| "expandAll"
| "indent"
| "last"
| "left"
| "moveDown"
| "moveUp"
| "outdent"
| "pageDown"
| "pageUp"
| "parent"
| "paste"
| "remove"
| "rename"
| "right"
| "up";
| "toggleSelect";

export type NodeFilterResponse = "skip" | "branch" | boolean | void;
export type NodeFilterCallback = (node: WunderbaumNode) => NodeFilterResponse;
Expand Down Expand Up @@ -607,6 +621,13 @@ export enum NavModeEnum {
row = "row",
}

/** */
export type TranslationsType = {
loading: "Loading...";
loadError: "Error";
noData: "No data";
queryResult: "Matched ${match} of ${count} nodes.";
};
/* -----------------------------------------------------------------------------
* METHOD OPTIONS TYPES
* ---------------------------------------------------------------------------*/
Expand Down Expand Up @@ -897,6 +918,19 @@ export interface VisitRowsOptions {
/* -----------------------------------------------------------------------------
* wb_ext_filter
* ---------------------------------------------------------------------------*/

/**
* Passed as tree option.filer.connect to configure automatic integration of
* filter UI controls.
*/
export interface FilterConnectType {
inputElem: string | HTMLInputElement | null;
modeButton?: string | HTMLButtonElement | HTMLAnchorElement | null;
nextButton?: string | HTMLButtonElement | HTMLAnchorElement | null;
prevButton?: string | HTMLButtonElement | HTMLAnchorElement | null;
matchInfoElem?: string | HTMLElement | null;
}

/**
* Passed as tree options to configure default filtering behavior.
*
Expand All @@ -908,7 +942,7 @@ export type FilterOptionsType = {
* Element or selector of an input control for filter query strings
* @default null
*/
connectInput?: null | string | Element;
connect?: null | FilterConnectType;
/**
* Re-apply last filter if lazy data is loaded
* @default true
Expand Down
13 changes: 0 additions & 13 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,19 +424,6 @@ export function elemFromSelector<T = HTMLElement>(obj: string | T): T | null {
return obj as T;
}

// /** Return a EventTarget from selector or cast an existing element. */
// export function eventTargetFromSelector(
// obj: string | EventTarget
// ): EventTarget | null {
// if (!obj) {
// return null;
// }
// if (typeof obj === "string") {
// return document.querySelector(obj) as EventTarget;
// }
// return obj as EventTarget;
// }

/**
* Return a canonical descriptive string for a keyboard or mouse event.
*
Expand Down
59 changes: 48 additions & 11 deletions src/wb_ext_filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
onEvent,
} from "./util";
import {
FilterConnectType,
FilterNodesOptions,
FilterOptionsType,
NodeFilterCallback,
Expand All @@ -29,15 +30,19 @@ const RE_START_MARKER = new RegExp(escapeRegex(START_MARKER), "g");
const RE_END_MARTKER = new RegExp(escapeRegex(END_MARKER), "g");

export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
public queryInput?: HTMLInputElement;
public queryInput: HTMLInputElement | null = null;
public prevButton: HTMLElement | null = null;
public nextButton: HTMLButtonElement | HTMLAnchorElement | null = null;
public modeButton: HTMLButtonElement | HTMLAnchorElement | null = null;
public matchInfoElem: HTMLElement | null = null;
public lastFilterArgs: IArguments | null = null;

constructor(tree: Wunderbaum) {
super(tree, "filter", {
autoApply: true, // Re-apply last filter if lazy data is loaded
autoExpand: false, // Expand all branches that contain matches while filtered
matchBranch: false, // Whether to implicitly match all children of matched nodes
connectInput: null, // Element or selector of an input control for filter query strings
connect: null, // Element or selector of an input control for filter query strings
fuzzy: false, // Match single characters in order, e.g. 'fb' will match 'FooBar'
hideExpanders: false, // Hide expanders if all child nodes are hidden by filter
highlight: true, // Highlight matches by wrapping inside <mark> tags
Expand All @@ -49,18 +54,45 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {

init() {
super.init();
const connectInput = this.getPluginOption("connectInput");
if (connectInput) {
this.queryInput = elemFromSelector(connectInput) as HTMLInputElement;
assert(
this.queryInput,
`Invalid 'filter.connectInput' option: ${connectInput}.`
);
const tree = this.tree;
const connect: FilterConnectType = this.getPluginOption("connect");
if (connect) {
this.queryInput = elemFromSelector(connect.inputElem);
if (!this.queryInput) {
throw new Error(
`Invalid 'filter.connect' option: ${connect.inputElem}.`
);
}
this.prevButton = elemFromSelector(connect.prevButton!);
this.nextButton = elemFromSelector(connect.nextButton!);
this.modeButton = elemFromSelector(connect.modeButton!);
this.matchInfoElem = elemFromSelector(connect.matchInfoElem!);
if (this.prevButton) {
onEvent(this.prevButton, "click", () => {
tree.findRelatedNode(
tree.getActiveNode() || tree.getFirstChild()!,
"prevMatch"
);
});
}
if (this.nextButton) {
onEvent(this.nextButton, "click", () => {
tree.findRelatedNode(
tree.getActiveNode() || tree.getFirstChild()!,
"nextMatch"
);
});
}
if (this.modeButton) {
onEvent(this.modeButton, "click", () => {
throw new Error("Not implemented");
});
}
onEvent(
this.queryInput,
"input",
debounce((e) => {
// this.tree.log("query", e);
// tree.log("query", e);
this.filterNodes(this.queryInput!.value.trim(), {});
}, 700)
);
Expand All @@ -72,7 +104,8 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
super.setPluginOption(name, value);
switch (name) {
case "mode":
this.tree.filterMode = value === "hide" ? "hide" : "dim";
this.tree.filterMode =
value === "hide" ? "hide" : value === "mark" ? "mark" : "dim";
this.tree.updateFilter();
break;
}
Expand Down Expand Up @@ -252,6 +285,10 @@ export class FilterExtension extends WunderbaumExtension<FilterOptionsType> {
tree.logDebug(
`Filter '${filter}' found ${count} nodes in ${Date.now() - start} ms.`
);
const info = treeOpts.strings?.queryResult
.replace("${match}", "" + count) //this.countMatches())
.replace("${count}", "" + tree.count());
tree.log(info);
return count;
}

Expand Down
17 changes: 9 additions & 8 deletions src/wb_node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
MakeVisibleOptions,
MatcherCallback,
NavigateOptions,
NavigationType,
NodeAnyCallback,
NodeStatusType,
NodeStringCallback,
Expand All @@ -46,7 +47,7 @@ import {
import {
decompressSourceData,
ICON_WIDTH,
KEY_TO_ACTION_DICT,
KEY_TO_NAVIGATION_MAP,
makeNodeTitleMatcher,
nodeTitleSorter,
RESERVED_TREE_SOURCE_KEYS,
Expand Down Expand Up @@ -656,7 +657,7 @@ export class WunderbaumNode {
*
* @see {@link Wunderbaum.findRelatedNode|tree.findRelatedNode()}
*/
findRelatedNode(where: string, includeHidden = false) {
findRelatedNode(where: NavigationType, includeHidden = false) {
return this.tree.findRelatedNode(this, where, includeHidden);
}

Expand Down Expand Up @@ -1509,12 +1510,12 @@ export class WunderbaumNode {
* e.g. `ArrowLeft` = 'left'.
* @param options
*/
async navigate(where: string, options?: NavigateOptions) {
async navigate(where: NavigationType | string, options?: NavigateOptions) {
// Allow to pass 'ArrowLeft' instead of 'left'
where = KEY_TO_ACTION_DICT[where] || where;
const navType = (KEY_TO_NAVIGATION_MAP[where] ?? where) as NavigationType;

// Otherwise activate or focus the related node
const node = this.findRelatedNode(where);
const node = this.findRelatedNode(navType);
if (!node) {
this.logWarn(`Could not find related node '${where}'.`);
return Promise.resolve(this);
Expand Down Expand Up @@ -2664,7 +2665,7 @@ export class WunderbaumNode {
_setStatusNode({
statusNodeType: status,
title:
tree.options.strings.loading +
tree.options.strings!.loading +
(message ? " (" + message + ")" : ""),
checkbox: false,
colspan: true,
Expand All @@ -2677,7 +2678,7 @@ export class WunderbaumNode {
_setStatusNode({
statusNodeType: status,
title:
tree.options.strings.loadError +
tree.options.strings!.loadError +
(message ? " (" + message + ")" : ""),
checkbox: false,
colspan: true,
Expand All @@ -2690,7 +2691,7 @@ export class WunderbaumNode {
case "noData":
_setStatusNode({
statusNodeType: status,
title: message || tree.options.strings.noData,
title: message || tree.options.strings!.noData,
checkbox: false,
colspan: true,
tooltip: details,
Expand Down
4 changes: 3 additions & 1 deletion src/wb_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
NavModeEnum,
NodeTypeDefinitionMap,
SelectModeType,
TranslationsType,
WbActivateEventType,
WbButtonClickEventType,
WbCancelableEventResultType,
Expand Down Expand Up @@ -119,10 +120,11 @@ export interface WunderbaumOptions {
* loading: "Loading...",
* loadError: "Error",
* noData: "No data",
* queryResult: "Matched ${match} of ${count} nodes.",
* }
* ```
*/
strings?: any; //[key: string] string;
strings?: TranslationsType;
/**
* 0:quiet, 1:errors, 2:warnings, 3:info, 4:verbose
* Default: 3 (4 in local debug environment)
Expand Down
Loading
Loading