Browse Source

Adds an actionbar to the bottom of the graph

main
Keith Daulton 2 years ago
parent
commit
4bfc55acbd
7 changed files with 307 additions and 28 deletions
  1. +15
    -0
      src/plus/webviews/graph/graphWebview.ts
  2. +3
    -0
      src/plus/webviews/graph/protocol.ts
  3. +1
    -0
      src/storage.ts
  4. +122
    -27
      src/webviews/apps/plus/graph/GraphWrapper.tsx
  5. +6
    -0
      src/webviews/apps/plus/graph/graph.html
  6. +154
    -1
      src/webviews/apps/plus/graph/graph.scss
  7. +6
    -0
      src/webviews/apps/plus/graph/graph.tsx

+ 15
- 0
src/plus/webviews/graph/graphWebview.ts View File

@ -32,6 +32,7 @@ import {
DidChangeCommitsNotificationType,
DidChangeConfigNotificationType,
DidChangeNotificationType,
DismissPreviewCommandType,
MoreCommitsCommandType,
SelectRepositoryCommandType,
UpdateSelectionCommandType,
@ -51,6 +52,7 @@ export class GraphWebview extends WebviewWithConfigBase {
private currentLog?: GitLog;
private repoDisposable: Disposable | undefined;
private defaultTitle?: string;
private previewBanner?: boolean;
constructor(container: Container) {
super(container, 'gitlens.graph', 'graph.html', 'images/gitlens-icon.png', 'Graph', Commands.ShowGraphPage);
@ -77,9 +79,17 @@ export class GraphWebview extends WebviewWithConfigBase {
case UpdateSelectionCommandType.method:
onIpc(UpdateSelectionCommandType, e, params => this.onSelectionChanged(params.selection));
break;
case DismissPreviewCommandType.method:
onIpc(DismissPreviewCommandType, e, () => this.dismissPreview());
break;
}
}
private dismissPreview() {
this.previewBanner = false;
void this.container.storage.storeWorkspace(WorkspaceStorageKeys.GraphPreview, false);
}
private changeColumn(name: string, config: GraphColumnConfig) {
const columns =
this.container.storage.getWorkspace<GraphColumnConfigDictionary>(WorkspaceStorageKeys.GraphColumns) ?? {};
@ -289,6 +299,10 @@ export class GraphWebview extends WebviewWithConfigBase {
};
}
if (this.previewBanner == null) {
this.previewBanner = (await this.container.storage.getWorkspace(WorkspaceStorageKeys.GraphPreview)) ?? true;
}
if (this.selectedRepository === undefined) {
const idealRepo = await this.pickRepository(repositories);
this.selectedRepository = idealRepo;
@ -318,6 +332,7 @@ export class GraphWebview extends WebviewWithConfigBase {
);
return {
previewBanner: this.previewBanner,
repositories: formatRepositories(repositories),
selectedRepository: this.selectedRepository?.path,
commits: formatCommits(combinedCommitsWithFilteredStashes),

+ 3
- 0
src/plus/webviews/graph/protocol.ts View File

@ -11,6 +11,7 @@ export interface State {
log?: GraphLog;
nonce?: string;
mixedColumnColors?: { [variable: string]: string };
previewBanner?: boolean;
}
export interface GraphLog {
@ -62,6 +63,8 @@ export interface SelectRepositoryParams {
}
export const SelectRepositoryCommandType = new IpcCommandType<SelectRepositoryParams>('graph/selectRepository');
export const DismissPreviewCommandType = new IpcCommandType<undefined>('graph/dismissPreview');
export interface UpdateSelectionParams {
selection: GraphCommit[];
}

+ 1
- 0
src/storage.ts View File

@ -122,6 +122,7 @@ export const enum WorkspaceStorageKeys {
ViewsSearchAndComparePinnedItems = 'gitlens:views:searchAndCompare:pinned',
ViewsCommitDetailsAutolinksExpanded = 'gitlens:views:commitDetails:autolinksExpanded',
GraphColumns = 'gitlens:graph:columns',
GraphPreview = 'gitlens:graph:preview',
Deprecated_DisallowConnectionPrefix = 'gitlens:disallow:connection:',
Deprecated_PinnedComparisons = 'gitlens:pinned:comparisons',

+ 122
- 27
src/webviews/apps/plus/graph/GraphWrapper.tsx View File

@ -27,6 +27,7 @@ export interface GraphWrapperProps extends State {
onSelectRepository?: (repository: GraphRepository) => void;
onColumnChange?: (name: string, settings: GraphColumnConfig) => void;
onMoreCommits?: (limit?: number) => void;
onDismissPreview?: () => void;
onSelectionChange?: (selection: GraphCommit[]) => void;
}
@ -173,16 +174,20 @@ export function GraphWrapper({
selectedRepository,
config,
log,
// onSelectRepository,
onSelectRepository,
onColumnChange,
onMoreCommits,
onSelectionChange,
nonce,
mixedColumnColors,
previewBanner = true,
onDismissPreview,
}: GraphWrapperProps) {
const [graphList, setGraphList] = useState(getGraphModel(commits, remotes, tags, branches));
const [_reposList, setReposList] = useState(repositories);
const [currentRepository, setCurrentRepository] = useState(selectedRepository);
const [reposList, setReposList] = useState(repositories);
const [currentRepository, setCurrentRepository] = useState<GraphRepository | undefined>(
reposList.find(item => item.path === selectedRepository),
);
const [graphColSettings, setGraphColSettings] = useState(getGraphColSettingsModel(config));
const [logState, setLogState] = useState(log);
const [isLoading, setIsLoading] = useState(false);
@ -192,6 +197,9 @@ export function GraphWrapper({
const [mainWidth, setMainWidth] = useState<number>();
const [mainHeight, setMainHeight] = useState<number>();
const mainRef = useRef<HTMLElement>(null);
const [showBanner, setShowBanner] = useState(previewBanner);
// repo selection UI
const [repoExpanded, setRepoExpanded] = useState(false);
useEffect(() => {
if (mainRef.current === null) {
@ -218,7 +226,7 @@ export function GraphWrapper({
function transformData(state: State) {
setGraphList(getGraphModel(state.commits, state.remotes, state.tags, state.branches));
setReposList(state.repositories ?? []);
setCurrentRepository(state.selectedRepository);
setCurrentRepository(reposList.find(item => item.path === state.selectedRepository));
setGraphColSettings(getGraphColSettingsModel(state.config));
setLogState(state.log);
setIsLoading(false);
@ -232,6 +240,18 @@ export function GraphWrapper({
return subscriber(transformData);
}, []);
const handleSelectRepository = (item: GraphRepository) => {
if (item != null && item !== currentRepository) {
onSelectRepository?.(item);
}
setRepoExpanded(false);
};
const handleToggleRepos = () => {
if (currentRepository != null && reposList.length <= 1) return;
setRepoExpanded(!repoExpanded);
};
const handleMoreCommits = () => {
setIsLoading(true);
onMoreCommits?.();
@ -245,30 +265,105 @@ export function GraphWrapper({
onSelectionChange?.(graphRows);
};
const handleDismissBanner = () => {
setShowBanner(false);
onDismissPreview?.();
};
return (
<main ref={mainRef} id="main" className="graph-app__main">
{currentRepository !== undefined ? (
<>
{mainWidth !== undefined && mainHeight !== undefined && (
<GraphContainer
columnsSettings={graphColSettings}
cssVariables={styleProps.cssVariables}
graphRows={graphList}
height={mainHeight}
hasMoreCommits={logState?.hasMore}
isLoadingRows={isLoading}
nonce={nonce}
onColumnResized={handleOnColumnResized}
onSelectGraphRows={handleSelectGraphRows}
onShowMoreCommits={handleMoreCommits}
width={mainWidth}
themeOpacityFactor={styleProps.themeOpacityFactor}
/>
)}
</>
) : (
<p>No repository is selected</p>
<>
{showBanner && (
<section className="graph-app__banner">
<div className="alert">
<span className="alert__icon codicon codicon-preview"></span>
<div className="alert__content">
<p className="alert__title">Preview</p>
<p className="alert__message">
This is a GitLens+ feature that requires a paid account for use on private repositories.
</p>
</div>
<button className="alert__action" type="button" onClick={() => handleDismissBanner()}>
<span className="codicon codicon-chrome-close"></span>
</button>
</div>
</section>
)}
</main>
<main ref={mainRef} id="main" className="graph-app__main">
{currentRepository !== undefined ? (
<>
{mainWidth !== undefined && mainHeight !== undefined && (
<GraphContainer
columnsSettings={graphColSettings}
cssVariables={styleProps.cssVariables}
graphRows={graphList}
height={mainHeight}
hasMoreCommits={logState?.hasMore}
isLoadingRows={isLoading}
nonce={nonce}
onColumnResized={handleOnColumnResized}
onSelectGraphRows={handleSelectGraphRows}
onShowMoreCommits={handleMoreCommits}
width={mainWidth}
themeOpacityFactor={styleProps.themeOpacityFactor}
/>
)}
</>
) : (
<p>No repository is selected</p>
)}
</main>
<footer className="actionbar graph-app__footer">
<div className="actioncombo">
<button
type="button"
aria-controls="repo-actioncombo-list"
aria-expanded={repoExpanded}
aria-haspopup="listbox"
id="repo-actioncombo-label"
className="actioncombo__label"
role="combobox"
aria-activedescendant=""
onClick={() => handleToggleRepos()}
>
<span className="codicon codicon-repo actioncombo__icon" aria-label="Repository "></span>
{currentRepository?.formattedName ?? 'none selected'}
</button>
<div
className="actioncombo__list"
id="repo-actioncombo-list"
role="listbox"
tabIndex={-1}
aria-labelledby="repo-actioncombo-label"
>
{reposList.length > 0 ? (
reposList.map((item, index) => (
<button
type="button"
className="actioncombo__item"
role="option"
data-value={item.path}
id={`repo-actioncombo-item-${index}`}
key={`repo-actioncombo-item-${index}`}
aria-selected={item.path === currentRepository?.path}
onClick={() => handleSelectRepository(item)}
disabled={item.path === currentRepository?.path}
>
{item.formattedName}
</button>
))
) : (
<li
className="actioncombo__item"
role="option"
id="repo-actioncombo-item-0"
aria-selected="true"
>
None available
</li>
)}
</div>
</div>
</footer>
</>
);
}

+ 6
- 0
src/webviews/apps/plus/graph/graph.html View File

@ -9,5 +9,11 @@
<p>A repository must be selected.</p>
</div>
#{endOfBody}
<style nonce="#{cspNonce}">
@font-face {
font-family: 'codicon';
src: url('#{webroot}/codicon.ttf?404cbc4fe3a64b9a93064eef76704c79') format('truetype');
}
</style>
</body>
</html>

+ 154
- 1
src/webviews/apps/plus/graph/graph.scss View File

@ -8,6 +8,150 @@
@import '../../shared/utils';
@import '../../../../../node_modules/@gitkraken/gitkraken-components/dist/styles.css';
body {
&.vscode-dark {
--actionbar-background-color: var(--color-background--lighten-05);
--actionbar-hover-background-color: var(--color-background--lighten-075);
}
&.vscode-light {
--actionbar-background-color: var(--color-background--darken-05);
--actionbar-hover-background-color: var(--color-background--darken-075);
}
}
.actionbar {
--actionbar-height: 22px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
gap: 0.5rem;
padding: 0 1rem;
height: var(--actionbar-height);
background-color: var(--actionbar-background-color);
color: var(--color-foreground);
> * {
margin: 0;
}
}
.actioncombo {
$block: &;
$block-expanded: #{$block}--expanded;
--actioncombo-height: 2.2rem;
--actioncombo-items: 1;
position: relative;
display: inline-flex;
flex-direction: row;
align-items: stretch;
font-size: 1.2rem;
gap: 0.25rem;
height: var(--actioncombo-height);
line-height: var(--actioncombo-height);
&__label,
&__item {
appearance: none;
font-family: inherit;
background-color: transparent;
border: none;
color: inherit;
height: var(--actioncombo-height);
line-height: var(--actioncombo-height);
cursor: pointer;
background-color: var(--actionbar-background-color);
text-align: left;
&:hover {
background-color: var(--actionbar-hover-background-color);
}
&[disabled] {
pointer-events: none;
opacity: 0.5;
}
}
&__label {
}
&__icon.codicon[class*='codicon-'] {
margin-right: 0.25rem;
}
&__list {
position: absolute;
left: 0;
bottom: 100%;
display: flex;
flex-direction: column;
justify-content: stretch;
min-width: 100%;
width: max-content;
z-index: 100;
}
&__label:not([aria-expanded='true']) + &__list {
display: none;
}
&__item {
height: var(--actioncombo-height);
line-height: var(--actioncombo-height);
padding: 0 0.75rem;
}
}
.alert {
--alert-foreground: var(--vscode-foreground);
--alert-background: transparent;
--alert-border: var(--vscode-foreground);
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
gap: 1rem;
padding: 1rem;
border-radius: 0.25rem;
border: 1px solid var(--vscode-foreground);
&__icon {
}
&__content {
padding-top: 0.24rem;
}
&__title {
font-size: 1.2rem;
font-weight: 700;
margin: 0;
}
&__message {
margin: 0;
}
&__title + &__message {
margin-top: 0.25rem;
}
&__action {
border: 1px solid transparent;
background-color: transparent;
color: inherit;
appearance: none;
width: 2rem;
height: 2rem;
padding: 0;
cursor: pointer;
margin-left: auto;
}
}
.graph-app {
padding: 0;
@ -15,10 +159,19 @@
display: flex;
flex-direction: column;
height: 100vh;
gap: 0.5rem;
gap: 0;
padding: 0 2px;
}
&__banner {
flex: none;
padding: 0.5rem;
}
&__footer {
flex: none;
}
&__main {
flex: 1 1 auto;
overflow: hidden;

+ 6
- 0
src/webviews/apps/plus/graph/graph.tsx View File

@ -15,6 +15,7 @@ import {
DidChangeCommitsNotificationType,
DidChangeConfigNotificationType,
DidChangeNotificationType,
DismissPreviewCommandType,
MoreCommitsCommandType,
SelectRepositoryCommandType,
UpdateSelectionCommandType,
@ -51,6 +52,7 @@ export class GraphApp extends App {
onSelectRepository={debounce((path: GraphRepository) => this.onRepositoryChanged(path), 250)}
onMoreCommits={(...params) => this.onMoreCommits(...params)}
onSelectionChange={debounce((selection: GraphCommit[]) => this.onSelectionChanged(selection), 250)}
onDismissPreview={() => this.onDismissPreview()}
{...this.state}
/>,
$root,
@ -141,6 +143,10 @@ export class GraphApp extends App {
this.refresh(this.state);
}
private onDismissPreview() {
this.sendCommand(DismissPreviewCommandType, undefined);
}
private onColumnChanged(name: string, settings: GraphColumnConfig) {
this.sendCommand(ColumnChangeCommandType, {
name: name,

Loading…
Cancel
Save