mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Introducing search store
Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
parent
118ed207a1
commit
5c084b8557
@ -8,9 +8,9 @@ import { Icon } from "../icon";
|
||||
import { _i18n } from "../../i18n";
|
||||
import { cssNames, downloadFile } from "../../utils";
|
||||
import { Pod } from "../../api/endpoints";
|
||||
import { PodLogSearch } from "./pod-log-search";
|
||||
import { PodLogSearch, PodLogSearchProps } from "./pod-log-search";
|
||||
|
||||
interface Props {
|
||||
interface Props extends PodLogSearchProps {
|
||||
ready: boolean
|
||||
tabId: string
|
||||
tabData: IPodLogsData
|
||||
@ -18,7 +18,6 @@ interface Props {
|
||||
save: (data: Partial<IPodLogsData>) => void
|
||||
reload: () => void
|
||||
onSearch: (query: string) => void
|
||||
search: string
|
||||
}
|
||||
|
||||
export const PodLogControls = observer((props: Props) => {
|
||||
@ -114,7 +113,7 @@ export const PodLogControls = observer((props: Props) => {
|
||||
onClick={downloadLogs}
|
||||
tooltip={_i18n._(t`Save`)}
|
||||
/>
|
||||
<PodLogSearch onSearch={props.onSearch} search={props.search} />
|
||||
<PodLogSearch {...props} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -2,24 +2,45 @@ import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { cssNames } from "../../utils";
|
||||
import { Input } from "../input";
|
||||
import { Button } from "@material-ui/core";
|
||||
import { searchStore } from "./search.store";
|
||||
|
||||
interface Props {
|
||||
export interface PodLogSearchProps {
|
||||
onSearch: (query: string) => void
|
||||
search: string
|
||||
toPrevOverlay: () => void
|
||||
toNextOverlay: () => void
|
||||
logs: string[]
|
||||
}
|
||||
|
||||
export const PodLogSearch = observer((props: Props) => {
|
||||
const { onSearch, search } = props;
|
||||
export const PodLogSearch = observer((props: PodLogSearchProps) => {
|
||||
const { logs, onSearch, toPrevOverlay, toNextOverlay } = props;
|
||||
const { setNextOverlayActive, setPrevOverlayActive } = searchStore;
|
||||
|
||||
const setSearch = (query: string) => {
|
||||
searchStore.onSearch(logs, query);
|
||||
onSearch(query);
|
||||
};
|
||||
|
||||
const onPrevOverlay = () => {
|
||||
setPrevOverlayActive();
|
||||
toPrevOverlay();
|
||||
}
|
||||
|
||||
const onNextOverlay = () => {
|
||||
setNextOverlayActive();
|
||||
toNextOverlay();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="PodLogsSearch">
|
||||
<Input
|
||||
className={cssNames("SearchInput")}
|
||||
value={search}
|
||||
value={searchStore.searchQuery}
|
||||
onChange={setSearch}
|
||||
/>
|
||||
{/* <span>{activeOverlay} / {totalOverlays}</span> */}
|
||||
<Button onClick={onPrevOverlay}>prev</Button>
|
||||
<Button onClick={onNextOverlay}>next</Button>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
});
|
||||
@ -40,6 +40,10 @@
|
||||
border-radius: 2px;
|
||||
background-color: #8cc474b8;
|
||||
-webkit-font-smoothing: auto;
|
||||
|
||||
&.active {
|
||||
background-color: orange;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import { IPodLogsData, logRange, podLogsStore } from "./pod-logs.store";
|
||||
import { Button } from "../button";
|
||||
import { PodLogControls } from "./pod-log-controls";
|
||||
import { VirtualList } from "../virtual-list";
|
||||
import { searchStore } from "./search.store";
|
||||
import debounce from "lodash/debounce";
|
||||
|
||||
interface Props {
|
||||
@ -27,7 +28,6 @@ export class PodLogs extends React.Component<Props> {
|
||||
@observable ready = false;
|
||||
@observable preloading = false; // Indicator for setting Spinner (loader) at the top of the logs
|
||||
@observable showJumpToBottom = false;
|
||||
@observable findQuery = ""; // A text from search field
|
||||
|
||||
private logsElement = React.createRef<HTMLDivElement>(); // A reference for outer container in VirtualList
|
||||
private virtualListRef = React.createRef<VirtualList>(); // A reference for VirtualList component
|
||||
@ -104,11 +104,22 @@ export class PodLogs extends React.Component<Props> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updating findQuery observable
|
||||
* A function for various actions after search is happened
|
||||
* @param query {string} A text from search field
|
||||
*/
|
||||
@autobind()
|
||||
onSearch(query: string) {
|
||||
this.findQuery = query;
|
||||
this.toOverlay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolling to active overlay (search word highlight)
|
||||
*/
|
||||
@autobind()
|
||||
toOverlay() {
|
||||
const { activeOverlayLine } = searchStore;
|
||||
if (!this.virtualListRef.current || activeOverlayLine == -1) return;
|
||||
this.virtualListRef.current.scrollToItem(activeOverlayLine);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,21 +154,27 @@ export class PodLogs extends React.Component<Props> {
|
||||
|
||||
/**
|
||||
* A function is called by VirtualList for rendering each of the row
|
||||
* @param index {Number} index of the log element in logs array
|
||||
* @param rowIndex {Number} index of the log element in logs array
|
||||
* @returns A react element with a row itself
|
||||
*/
|
||||
getLogRow = (index: number) => {
|
||||
const isSeparator = this.logs[index] === "---newlogs---"; // TODO: Use constant separator
|
||||
const { findQuery } = this;
|
||||
const item = this.logs[index];
|
||||
getLogRow = (rowIndex: number) => {
|
||||
const isSeparator = this.logs[rowIndex] === "---newlogs---"; // TODO: Use constant separator
|
||||
const { searchQuery, isActiveOverlay } = searchStore;
|
||||
const item = this.logs[rowIndex];
|
||||
const contents: React.ReactElement[] = [];
|
||||
if (findQuery) {
|
||||
if (searchQuery) {
|
||||
// If search is enabled, replace keyword with backgrounded <span> to "highlight" searchable text
|
||||
const pieces = item.split(findQuery);
|
||||
const pieces = item.split(searchQuery);
|
||||
pieces.forEach((piece, index) => {
|
||||
const overlay = index !== pieces.length - 1 ? <span>{findQuery}</span> : null
|
||||
const active = isActiveOverlay(rowIndex, index);
|
||||
const lastItem = index === pieces.length - 1;
|
||||
const overlay = !lastItem ?
|
||||
<span className={cssNames({ active })}>{searchQuery}</span> :
|
||||
null
|
||||
contents.push(
|
||||
<>{piece}{overlay}</>
|
||||
<React.Fragment key={piece + index}>
|
||||
{piece}{overlay}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
}
|
||||
@ -231,8 +248,9 @@ export class PodLogs extends React.Component<Props> {
|
||||
logs={this.logs}
|
||||
save={this.save}
|
||||
reload={this.reload}
|
||||
search={this.findQuery}
|
||||
onSearch={this.onSearch}
|
||||
toPrevOverlay={this.toOverlay}
|
||||
toNextOverlay={this.toOverlay}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
|
||||
115
src/renderer/components/dock/search.store.ts
Normal file
115
src/renderer/components/dock/search.store.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { action, computed, observable } from "mobx";
|
||||
import { autobind } from "../../utils";
|
||||
|
||||
export class SearchStore {
|
||||
@observable searchQuery = ""; // Text in the search input
|
||||
@observable occurrences: number[] = []; // Array with line numbers, eg [0, 0, 10, 21, 21, 40...]
|
||||
@observable activeOverlayIndex = -1; // Index withing the occurences array. Showing where is activeOverlay currently located
|
||||
|
||||
/**
|
||||
* Sets default activeOverlayIndex
|
||||
* @param text An array of any textual data (logs, for example)
|
||||
* @param query Search query from input
|
||||
*/
|
||||
@action
|
||||
onSearch(text: string[], query: string) {
|
||||
this.searchQuery = query;
|
||||
if (!query) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
this.occurrences = this.findOccurencies(text, query);
|
||||
if (!this.occurrences.length) return;
|
||||
|
||||
// If new highlighted keyword in exact same place as previous one, then no changing in active overlay
|
||||
if (this.occurrences[this.activeOverlayIndex] !== undefined) return;
|
||||
this.activeOverlayIndex = this.getNextOverlay(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does searching within text array, create a list of search keyword occurences.
|
||||
* Each keyword "occurency" is saved as index of the the line where keyword founded
|
||||
* @param text An array of any textual data (logs, for example)
|
||||
* @param query Search query from input
|
||||
* @returns {Array} Array of line indexes [0, 0, 14, 17, 17, 17, 20...]
|
||||
*/
|
||||
findOccurencies(text: string[], query: string) {
|
||||
const occurences: number[] = [];
|
||||
text.forEach((line, index) => {
|
||||
const regex = new RegExp(this.escapeRegex(query), "g");
|
||||
const matches = [...line.matchAll(regex)];
|
||||
matches.forEach(() => occurences.push(index));
|
||||
});
|
||||
return occurences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting next overlay index within the occurences array
|
||||
* @param loopOver Allows to jump from last element to first
|
||||
* @returns {number} next overlay index
|
||||
*/
|
||||
getNextOverlay(loopOver = false) {
|
||||
const next = this.activeOverlayIndex + 1;
|
||||
if (next > this.occurrences.length - 1) {
|
||||
return loopOver ? 0 : this.activeOverlayIndex;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting previous overlay index within the occurences array of occurences
|
||||
* @param loopOver Allows to jump from first element to last one
|
||||
* @returns {number} prev overlay index
|
||||
*/
|
||||
getPrevOverlay(loopOver = false) {
|
||||
const prev = this.activeOverlayIndex - 1;
|
||||
if (prev < 0) {
|
||||
return loopOver ? this.occurrences.length - 1 : this.activeOverlayIndex;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
@autobind()
|
||||
setNextOverlayActive() {
|
||||
this.activeOverlayIndex = this.getNextOverlay(true);
|
||||
}
|
||||
|
||||
@autobind()
|
||||
setPrevOverlayActive() {
|
||||
this.activeOverlayIndex = this.getPrevOverlay(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets line index of where active overlay is located
|
||||
* @returns {number} A line index within the text/logs array
|
||||
*/
|
||||
@computed get activeOverlayLine(): number {
|
||||
return this.occurrences[this.activeOverlayIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if overlay is active (to highlight it with orange background usually)
|
||||
* @param line Index of the line where overlay is located
|
||||
* @param occurence Number of the overlay within one line
|
||||
*/
|
||||
@autobind()
|
||||
isActiveOverlay(line: number, occurence: number) {
|
||||
const firstLineIndex = this.occurrences.findIndex(item => item === line);
|
||||
return firstLineIndex + occurence === this.activeOverlayIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* An utility methods escaping user string to safely pass it into new Regex(variable)
|
||||
* @param value Unescaped string
|
||||
*/
|
||||
escapeRegex(value: string) {
|
||||
return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.searchQuery = "";
|
||||
this.activeOverlayIndex = -1
|
||||
}
|
||||
}
|
||||
|
||||
export const searchStore = new SearchStore;
|
||||
Loading…
Reference in New Issue
Block a user