Просмотр исходного кода

Adds vscode style progress indicator to the Graph

Fixes progress indicators when loading new rows & other updates happen (e.g. avatars)
main
Eric Amodio 2 лет назад
Родитель
Сommit
433e5c17c2
4 измененных файлов: 98 добавлений и 41 удалений
  1. +2
    -2
      src/plus/webviews/graph/protocol.ts
  2. +21
    -26
      src/webviews/apps/plus/graph/GraphWrapper.tsx
  3. +55
    -0
      src/webviews/apps/plus/graph/graph.scss
  4. +20
    -13
      src/webviews/apps/plus/graph/graph.tsx

+ 2
- 2
src/plus/webviews/graph/protocol.ts Просмотреть файл

@ -59,8 +59,8 @@ export interface GraphCompositeConfig extends GraphConfig {
columns?: Record<string, GraphColumnConfig>;
}
export interface CommitListCallback {
(state: State): void;
export interface UpdateStateCallback {
(state: State, oldState: State): void;
}
// Commands

+ 21
- 26
src/webviews/apps/plus/graph/GraphWrapper.tsx Просмотреть файл

@ -13,11 +13,11 @@ import type { GraphColumnConfig } from '../../../../config';
import { RepositoryVisibility } from '../../../../git/gitProvider';
import type { GitGraphRowType } from '../../../../git/models/graph';
import type {
CommitListCallback,
DismissBannerParams,
GraphCompositeConfig,
GraphRepository,
State,
UpdateStateCallback,
} from '../../../../plus/webviews/graph/protocol';
import type { Subscription } from '../../../../subscription';
import { getSubscriptionTimeRemaining, SubscriptionState } from '../../../../subscription';
@ -25,7 +25,7 @@ import { pluralize } from '../../../../system/string';
export interface GraphWrapperProps extends State {
nonce?: string;
subscriber: (callback: CommitListCallback) => () => void;
subscriber: (callback: UpdateStateCallback) => () => void;
onSelectRepository?: (repository: GraphRepository) => void;
onColumnChange?: (name: string, settings: GraphColumnConfig) => void;
onMissingAvatars?: (emails: { [email: string]: string }) => void;
@ -155,7 +155,7 @@ export function GraphWrapper({
trialBanner = true,
onDismissBanner,
}: GraphWrapperProps) {
const [graphList, setGraphList] = useState(rows);
const [graphRows, setGraphRows] = useState(rows);
const [graphAvatars, setAvatars] = useState(avatars);
const [reposList, setReposList] = useState(repositories);
const [currentRepository, setCurrentRepository] = useState<GraphRepository | undefined>(
@ -182,48 +182,40 @@ export function GraphWrapper({
const [repoExpanded, setRepoExpanded] = useState(false);
useEffect(() => {
if (mainRef.current === null) {
return;
}
if (mainRef.current === null) return;
const setDimensionsDebounced = debounceFrame((width, height) => {
setMainWidth(Math.floor(width));
setMainHeight(Math.floor(height) - graphHeaderOffset);
});
const resizeObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
setDimensionsDebounced(entry.contentRect.width, entry.contentRect.height);
});
});
const resizeObserver = new ResizeObserver(entries =>
entries.forEach(e => setDimensionsDebounced(e.contentRect.width, e.contentRect.height)),
);
resizeObserver.observe(mainRef.current);
return () => {
resizeObserver.disconnect();
};
return () => resizeObserver.disconnect();
}, [mainRef]);
function transformData(state: State) {
setGraphList(state.rows ?? []);
function transformData(state: State, oldState: State) {
if (!isLoading || oldState.rows !== state.rows) {
setIsLoading(state.rows == null);
}
setGraphRows(state.rows ?? []);
setAvatars(state.avatars ?? {});
setReposList(state.repositories ?? []);
setCurrentRepository(reposList.find(item => item.path === state.selectedRepository));
setSelectedRows(state.selectedRows);
setGraphColSettings(getGraphColSettingsModel(state.config));
setPagingState(state.paging);
setIsLoading(state.rows == null);
setStyleProps(getStyleProps(state.mixedColumnColors));
setIsAllowed(state.allowed ?? false);
setSubscriptionSnapshot(state.subscription);
setIsPrivateRepo(state.selectedRepositoryVisibility === RepositoryVisibility.Private);
}
useEffect(() => {
if (subscriber === undefined) {
return;
}
return subscriber(transformData);
}, []);
useEffect(() => subscriber?.(transformData), []);
const handleSelectRepository = (item: GraphRepository) => {
if (item != null && item !== currentRepository) {
@ -442,7 +434,7 @@ export function GraphWrapper({
cssVariables={styleProps.cssVariables}
getExternalIcon={getIconElementLibrary}
avatarUrlByEmail={graphAvatars}
graphRows={graphList}
graphRows={graphRows}
height={mainHeight}
isSelectedBySha={graphSelectedRows}
hasMoreCommits={pagingState?.more}
@ -520,9 +512,9 @@ export function GraphWrapper({
)}
</div>
</div>
{isAllowed && graphList.length > 0 && (
{isAllowed && graphRows.length > 0 && (
<span className="actionbar__details">
showing {graphList.length} item{graphList.length ? 's' : ''}
showing {graphRows.length} item{graphRows.length ? 's' : ''}
</span>
)}
{isLoading && (
@ -542,6 +534,9 @@ export function GraphWrapper({
<span className="codicon codicon-feedback"></span>
</a>
</div>
<div className={`progress-container infinite${isLoading ? ' active' : ''}`} role="progressbar">
<div className="progress-bar"></div>
</div>
</footer>
</>
);

+ 55
- 0
src/webviews/apps/plus/graph/graph.scss Просмотреть файл

@ -409,6 +409,7 @@ a {
&__footer {
flex: none;
position: relative;
}
&__main {
@ -499,3 +500,57 @@ a {
.mr-loose {
margin-right: 0.5rem;
}
.progress-container {
position: absolute;
left: 0;
bottom: -2px;
z-index: 5;
height: 2px;
width: 100%;
overflow: hidden;
& .progress-bar {
background-color: var(--vscode-progressBar-background);
display: none;
position: absolute;
left: 0;
width: 2%;
height: 2px;
}
&.active .progress-bar {
display: inherit;
}
&.discrete .progress-bar {
left: 0;
transition: width .1s linear;
}
&.discrete.done .progress-bar {
width: 100%;
}
&.infinite .progress-bar {
animation-name: progress;
animation-duration: 4s;
animation-iteration-count: infinite;
animation-timing-function: steps(100);
transform: translateZ(0);
}
}
@keyframes progress {
0% {
transform: translateX(0) scaleX(1);
}
50% {
transform: translateX(2500%) scaleX(3);
}
to {
transform: translateX(4900%) scaleX(1);
}
}

+ 20
- 13
src/webviews/apps/plus/graph/graph.tsx Просмотреть файл

@ -5,10 +5,10 @@ import { render, unmountComponentAtNode } from 'react-dom';
import type { GitGraphRowType } from 'src/git/models/graph';
import type { GraphColumnConfig } from '../../../../config';
import type {
CommitListCallback,
DismissBannerParams,
GraphRepository,
State,
UpdateStateCallback,
} from '../../../../plus/webviews/graph/protocol';
import {
DidChangeAvatarsNotificationType,
@ -46,7 +46,7 @@ const graphLaneThemeColors = new Map([
]);
export class GraphApp extends App<State> {
private callback?: CommitListCallback;
private callback?: UpdateStateCallback;
constructor() {
super('GraphApp');
@ -61,7 +61,7 @@ export class GraphApp extends App {
if ($root != null) {
render(
<GraphWrapper
subscriber={(callback: CommitListCallback) => this.registerEvents(callback)}
subscriber={(callback: UpdateStateCallback) => this.registerEvents(callback)}
onColumnChange={debounce(
(name: string, settings: GraphColumnConfig) => this.onColumnChanged(name, settings),
250,
@ -96,15 +96,17 @@ export class GraphApp extends App {
switch (msg.method) {
case DidChangeNotificationType.method:
onIpc(DidChangeNotificationType, msg, params => {
const old = this.state;
this.setState({ ...this.state, ...params.state });
this.refresh(this.state);
this.refresh(this.state, old);
});
break;
case DidChangeAvatarsNotificationType.method:
onIpc(DidChangeAvatarsNotificationType, msg, params => {
const old = this.state;
this.setState({ ...this.state, avatars: params.avatars });
this.refresh(this.state);
this.refresh(this.state, old);
});
break;
@ -172,38 +174,42 @@ export class GraphApp extends App {
}
}
const old = this.state;
this.setState({
...this.state,
avatars: params.avatars,
rows: rows,
paging: params.paging,
});
this.refresh(this.state);
this.refresh(this.state, old);
});
break;
case DidChangeSelectionNotificationType.method:
onIpc(DidChangeSelectionNotificationType, msg, params => {
const old = this.state;
this.setState({ ...this.state, selectedRows: params.selection });
this.refresh(this.state);
this.refresh(this.state, old);
});
break;
case DidChangeGraphConfigurationNotificationType.method:
onIpc(DidChangeGraphConfigurationNotificationType, msg, params => {
const old = this.state;
this.setState({ ...this.state, config: params.config });
this.refresh(this.state);
this.refresh(this.state, old);
});
break;
case DidChangeSubscriptionNotificationType.method:
onIpc(DidChangeSubscriptionNotificationType, msg, params => {
const old = this.state;
this.setState({
...this.state,
subscription: params.subscription,
allowed: params.allowed,
});
this.refresh(this.state);
this.refresh(this.state, old);
});
break;
@ -213,8 +219,9 @@ export class GraphApp extends App {
}
protected override onThemeUpdated() {
const old = this.state;
this.setState({ ...this.state, mixedColumnColors: undefined });
this.refresh(this.state);
this.refresh(this.state, old);
}
protected override setState(state: State) {
@ -283,7 +290,7 @@ export class GraphApp extends App {
});
}
private registerEvents(callback: CommitListCallback): () => void {
private registerEvents(callback: UpdateStateCallback): () => void {
this.callback = callback;
return () => {
@ -291,8 +298,8 @@ export class GraphApp extends App {
};
}
private refresh(state: State) {
this.callback?.(state);
private refresh(state: State, oldState: State) {
this.callback?.(state, oldState);
}
}

Загрузка…
Отмена
Сохранить