From c9070ac224b66fc720698e5b2c6abcde56af603e Mon Sep 17 00:00:00 2001 From: Keith Daulton Date: Thu, 22 Sep 2022 01:05:54 -0400 Subject: [PATCH] Updates graph search UI to use searchResults - adds check to ensure commit is in graph - moves input value validation to input handler - removes mock state --- src/plus/webviews/graph/graphWebview.ts | 23 ++++++++ src/plus/webviews/graph/protocol.ts | 12 ++++ src/webviews/apps/plus/graph/GraphWrapper.tsx | 85 +++++++++++++++++---------- src/webviews/apps/plus/graph/graph.tsx | 7 +++ 4 files changed, 95 insertions(+), 32 deletions(-) diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index 05f4efd..b3fc758 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -36,8 +36,10 @@ import { DidChangeNotificationType, DidChangeSelectionNotificationType, DidChangeSubscriptionNotificationType, + DidEnsureCommitNotificationType, DidSearchCommitsNotificationType, DismissBannerCommandType, + EnsureHasCommitCommandType, GetMissingAvatarsCommandType, GetMoreCommitsCommandType, SearchCommitsCommandType, @@ -211,6 +213,9 @@ export class GraphWebview extends WebviewBase { case UpdateSelectionCommandType.method: onIpc(UpdateSelectionCommandType, e, params => this.onSelectionChanged(params.selection)); break; + case EnsureHasCommitCommandType.method: + onIpc(EnsureHasCommitCommandType, e, params => this.onEnsureCommit(params.id, e.id)); + break; } } @@ -673,6 +678,24 @@ export class GraphWebview extends WebviewBase { this._selectedSha = sha; this._selectedRows = sha != null ? { [sha]: true } : {}; } + + private async onEnsureCommit(id: string, completionId: string) { + if (this._graph == null) return; + + if (!this._graph.ids.has(id)) { + const { defaultItemLimit, pageItemLimit } = configuration.get('graph'); + const newGraph = await this._graph.more!(pageItemLimit ?? defaultItemLimit, id); + if (newGraph != null) { + this.setGraph(newGraph); + } else { + debugger; + } + + void this.notifyDidChangeCommits(); + } + + void this.notify(DidEnsureCommitNotificationType, { id: id }, completionId); + } } function formatRepositories(repositories: Repository[]): GraphRepository[] { diff --git a/src/plus/webviews/graph/protocol.ts b/src/plus/webviews/graph/protocol.ts index ef01892..4ff3be7 100644 --- a/src/plus/webviews/graph/protocol.ts +++ b/src/plus/webviews/graph/protocol.ts @@ -112,6 +112,11 @@ export interface UpdateSelectionParams { } export const UpdateSelectionCommandType = new IpcCommandType('graph/update/selection'); +export interface EnsureHasCommitParams { + id: string; +} +export const EnsureHasCommitCommandType = new IpcCommandType('graph/update/ensureHasCommit'); + // Notifications export interface DidChangeParams { state: State; @@ -164,3 +169,10 @@ export interface DidSearchCommitsParams { export const DidSearchCommitsNotificationType = new IpcNotificationType( 'graph/commits/didSearch', ); + +export interface DidEnsureCommitParams { + id?: string; +} +export const DidEnsureCommitNotificationType = new IpcNotificationType( + 'graph/commits/didEnsureCommit', +); diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx index af8ec67..c503c90 100644 --- a/src/webviews/apps/plus/graph/GraphWrapper.tsx +++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx @@ -16,6 +16,7 @@ import { RepositoryVisibility } from '../../../../git/gitProvider'; import type { GitGraphRowType } from '../../../../git/models/graph'; import type { SearchPattern } from '../../../../git/search'; import type { + DidEnsureCommitParams, DismissBannerParams, GraphComponentConfig, GraphRepository, @@ -39,6 +40,7 @@ export interface GraphWrapperProps extends State { onSearchCommits?: (search: SearchPattern) => void; //Promise; onDismissBanner?: (key: DismissBannerParams['key']) => void; onSelectionChange?: (selection: { id: string; type: GitGraphRowType }[]) => void; + onEnsureCommit?: (id: string) => Promise; } const getStyleProps = ( @@ -91,11 +93,11 @@ const getGraphDateFormatter = (config?: GraphComponentConfig): OnFormatCommitDat return (commitDateTime: number) => formatCommitDateTime(commitDateTime, config?.dateStyle, config?.dateFormat); }; -const getSearchHighlights = (searchResults: State['searchResults']): { [id: string]: boolean } | undefined => { - if (!searchResults?.ids?.length) return undefined; +const getSearchHighlights = (searchIds?: string[]): { [id: string]: boolean } | undefined => { + if (!searchIds?.length) return undefined; const highlights: { [id: string]: boolean } = {}; - for (const sha of searchResults.ids) { + for (const sha of searchIds) { highlights[sha] = true; } return highlights; @@ -205,9 +207,10 @@ export function GraphWrapper({ nonce, mixedColumnColors, previewBanner = true, - searchResults: searchResults2, + searchResults, trialBanner = true, onDismissBanner, + onEnsureCommit, }: GraphWrapperProps) { const [graphRows, setGraphRows] = useState(rows); const [graphAvatars, setAvatars] = useState(avatars); @@ -240,58 +243,76 @@ export function GraphWrapper({ const [columnSettingsExpanded, setColumnSettingsExpanded] = useState(false); // search state const [searchValue, setSearchValue] = useState(''); - const [searchResults, setSearchResults] = useState([]); const [searchResultKey, setSearchResultKey] = useState(undefined); - const [searchHighlights, setSearchHighlights] = useState(getSearchHighlights(searchResults2)); + const [searchIds, setSearchIds] = useState(searchResults?.ids); useEffect(() => { - if (searchValue === '' || searchValue.length < 3 || graphRows.length < 1) { - setSearchResults([]); + if (graphRows.length === 0) { + setSearchIds(undefined); + } + }, [graphRows]); + + useEffect(() => { + if (searchIds == null) { setSearchResultKey(undefined); - setSearchHighlights(undefined); return; } - const results = graphRows.filter(row => row.message.toLowerCase().indexOf(searchValue.toLowerCase()) > 0); - setSearchResults(results); - - if ( - searchResultKey == null || - (searchResultKey != null && results.findIndex(row => row.sha === searchResultKey) === -1) - ) { - setSearchResultKey(results[0]?.sha); + if (searchResultKey == null || (searchResultKey != null && searchIds.includes(searchResultKey))) { + setSearchResultKey(searchIds[0]); } - }, [searchValue, graphRows]); + }, [searchIds]); + + const searchHighlights = useMemo(() => getSearchHighlights(searchIds), [searchIds]); const searchPosition: number = useMemo(() => { - if (searchResultKey == null) { - return 1; + if (searchResultKey == null || searchIds == null) { + return 0; } - const idx = searchResults.findIndex(row => row.sha === searchResultKey); + const idx = searchIds.indexOf(searchResultKey); if (idx < 1) { return 1; } return idx + 1; - }, [searchResultKey, searchResults]); + }, [searchResultKey, searchIds]); const handleSearchNavigation = (next = true) => { - const rowIndex = searchResultKey != null && searchResults.findIndex(row => row.sha === searchResultKey); - if (rowIndex === false) { - return; - } - if (next && rowIndex < searchResults.length - 1) { - setSearchResultKey(searchResults[rowIndex + 1].sha); + if (searchResultKey == null || searchIds == null) return; + + const rowIndex = searchIds.indexOf(searchResultKey); + if (rowIndex === -1) return; + + let nextSha: string | undefined; + if (next && rowIndex < searchIds.length - 1) { + nextSha = searchIds[rowIndex + 1]; } else if (!next && rowIndex > 0) { - setSearchResultKey(searchResults[rowIndex - 1].sha); + nextSha = searchIds[rowIndex - 1]; + } + + if (nextSha == null) return; + + if (onEnsureCommit != null) { + setIsLoading(true); + onEnsureCommit(nextSha).finally(() => { + setIsLoading(false); + setSearchResultKey(nextSha); + }); + } else { + setSearchResultKey(nextSha); } }; const handleSearchInput = (e: FormEvent) => { const currentValue = e.currentTarget.value; - setSearchValue(currentValue); + + if (currentValue.length < 3) { + setSearchResultKey(undefined); + setSearchIds(undefined); + return; + } onSearchCommits?.({ pattern: currentValue }); }; @@ -327,7 +348,7 @@ export function GraphWrapper({ setSubscriptionSnapshot(state.subscription); setIsPrivateRepo(state.selectedRepositoryVisibility === RepositoryVisibility.Private); setIsLoading(state.loading); - setSearchHighlights(getSearchHighlights(state.searchResults)); + setSearchIds(state.searchResults?.ids); } useEffect(() => subscriber?.(transformData), []); @@ -568,7 +589,7 @@ export function GraphWrapper({ handleSearchNavigation(false)} onNext={() => handleSearchNavigation(true)} /> diff --git a/src/webviews/apps/plus/graph/graph.tsx b/src/webviews/apps/plus/graph/graph.tsx index de054b8..4ea8f15 100644 --- a/src/webviews/apps/plus/graph/graph.tsx +++ b/src/webviews/apps/plus/graph/graph.tsx @@ -18,8 +18,10 @@ import { DidChangeNotificationType, DidChangeSelectionNotificationType, DidChangeSubscriptionNotificationType, + DidEnsureCommitNotificationType, DidSearchCommitsNotificationType, DismissBannerCommandType, + EnsureHasCommitCommandType, GetMissingAvatarsCommandType, GetMoreCommitsCommandType, SearchCommitsCommandType, @@ -81,6 +83,7 @@ export class GraphApp extends App { 250, )} onDismissBanner={key => this.onDismissBanner(key)} + onEnsureCommit={this.onEnsureCommit.bind(this)} {...this.state} />, $root, @@ -309,6 +312,10 @@ export class GraphApp extends App { return this.sendCommand(SearchCommitsCommandType, { search: search }); } + private onEnsureCommit(id: string) { + return this.sendCommandWithCompletion(EnsureHasCommitCommandType, { id: id }, DidEnsureCommitNotificationType); + } + private onSelectionChanged(selection: { id: string; type: GitGraphRowType }[]) { this.sendCommand(UpdateSelectionCommandType, { selection: selection,