diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index 4d4cf50..aecb699 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -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(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), diff --git a/src/plus/webviews/graph/protocol.ts b/src/plus/webviews/graph/protocol.ts index f5cb131..71bbb9b 100644 --- a/src/plus/webviews/graph/protocol.ts +++ b/src/plus/webviews/graph/protocol.ts @@ -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('graph/selectRepository'); +export const DismissPreviewCommandType = new IpcCommandType('graph/dismissPreview'); + export interface UpdateSelectionParams { selection: GraphCommit[]; } diff --git a/src/storage.ts b/src/storage.ts index 8855214..15173d6 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -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', diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx index bdd2c4b..28e7b1f 100644 --- a/src/webviews/apps/plus/graph/GraphWrapper.tsx +++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx @@ -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( + 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(); const [mainHeight, setMainHeight] = useState(); const mainRef = useRef(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 ( -
- {currentRepository !== undefined ? ( - <> - {mainWidth !== undefined && mainHeight !== undefined && ( - - )} - - ) : ( -

No repository is selected

+ <> + {showBanner && ( +
+
+ +
+

Preview

+

+ This is a GitLens+ feature that requires a paid account for use on private repositories. +

+
+ +
+
)} -
+
+ {currentRepository !== undefined ? ( + <> + {mainWidth !== undefined && mainHeight !== undefined && ( + + )} + + ) : ( +

No repository is selected

+ )} +
+
+
+ +
+ {reposList.length > 0 ? ( + reposList.map((item, index) => ( + + )) + ) : ( +
  • + None available +
  • + )} +
    +
    +
    + ); } diff --git a/src/webviews/apps/plus/graph/graph.html b/src/webviews/apps/plus/graph/graph.html index 94304a3..38c77d1 100644 --- a/src/webviews/apps/plus/graph/graph.html +++ b/src/webviews/apps/plus/graph/graph.html @@ -9,5 +9,11 @@

    A repository must be selected.

    #{endOfBody} + diff --git a/src/webviews/apps/plus/graph/graph.scss b/src/webviews/apps/plus/graph/graph.scss index 35e901d..a87b699 100644 --- a/src/webviews/apps/plus/graph/graph.scss +++ b/src/webviews/apps/plus/graph/graph.scss @@ -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; diff --git a/src/webviews/apps/plus/graph/graph.tsx b/src/webviews/apps/plus/graph/graph.tsx index 6d121f3..ffa45c7 100644 --- a/src/webviews/apps/plus/graph/graph.tsx +++ b/src/webviews/apps/plus/graph/graph.tsx @@ -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,