Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List some branches #1126

Merged
merged 49 commits into from
Apr 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
72450eb
Extract branch list from branch foldout component
niik Apr 3, 2017
4e65870
:fire: obsolete border
niik Apr 3, 2017
358310f
Make the branches list styles stand-alone
niik Apr 3, 2017
3aab6da
Use branch list in merge dialog
niik Apr 3, 2017
2e9b829
Remove obsolete styles
niik Apr 3, 2017
9af6210
Give that merge dialog an id
niik Apr 3, 2017
627c7dd
Style button group, not whole footer
niik Apr 3, 2017
10368ea
Add initial merge dialog styles
niik Apr 3, 2017
bbea319
Wider
niik Apr 3, 2017
97b639c
Enable experimental features for ResizeObserver
niik Apr 4, 2017
a9a63fc
Use ResizeObserver when available instead of AutoSizer
niik Apr 4, 2017
c1f86dc
Fix interactions on scroll bar on Windows
niik Apr 4, 2017
9d2961b
Remove one layer of nesting in lists on Windows
niik Apr 4, 2017
2eef0a3
Add an event for selection changed on filter list
niik Apr 4, 2017
5ef1ab1
Use props to determine selected branch
niik Apr 4, 2017
8a2cc7d
Only check out branch if it's not already checked out
niik Apr 4, 2017
6ac4967
Select current branch initially
niik Apr 4, 2017
d367136
Keep track of selection
niik Apr 4, 2017
ff5d43c
Branch list keeps track of selection
niik Apr 4, 2017
380adb4
Select the default branch by default
niik Apr 4, 2017
e6d2a13
Keep track of selection in merge
niik Apr 4, 2017
47395fb
Dismiss merge dialog on escape
niik Apr 4, 2017
e453636
Notify consumers if selection has changed due to filtering
niik Apr 4, 2017
08481e2
Better types
niik Apr 4, 2017
5ca1e14
Only update commit count if we have a selection
niik Apr 4, 2017
4bbea8b
This shouldn't work but it does... why?
niik Apr 4, 2017
6283322
And this is the correct version
niik Apr 4, 2017
67fbac4
Ditch the cancel button :/
niik Apr 4, 2017
3ac6ab3
Clear out old code
niik Apr 4, 2017
e2ea315
Some styling of the merge dialog
niik Apr 4, 2017
d3b772c
Secondary color and alignment for info text
niik Apr 4, 2017
7c8a0e7
Cleanup list header styles
niik Apr 4, 2017
cd8d451
line it all up
niik Apr 4, 2017
a334069
Better padding
niik Apr 4, 2017
f56f78a
Style the merge info
niik Apr 4, 2017
a8f7532
Readonly and :book:
niik Apr 4, 2017
447d2af
:book:
niik Apr 4, 2017
89360a0
:fire: whitespace
niik Apr 4, 2017
5bfbc79
Revert test change
niik Apr 4, 2017
95df1ee
:book: IBranchesState
niik Apr 4, 2017
88ecf8d
:book:
niik Apr 4, 2017
58dd4d4
Don't render previous commit count while calculating
niik Apr 4, 2017
f9313d8
:book:
niik Apr 4, 2017
4f9e132
:art: cleanup
niik Apr 4, 2017
e33f09d
Merge remote-tracking branch 'origin/master' into list-some-branches
niik Apr 5, 2017
c076478
Sentence case on Windows
niik Apr 5, 2017
6d2c7a0
:art: typo
niik Apr 5, 2017
613eb53
Optional onItemClick prop
niik Apr 5, 2017
8b70b50
:art: indentation
niik Apr 5, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/src/lib/app-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,31 @@ export interface IRepositoryState {
}

export interface IBranchesState {
/**

This comment was marked as spam.

* The current tip of HEAD, either a branch, a commit (if HEAD is
* detached) or an unborn branch (a branch with no commits).
*/
readonly tip: Tip

/**
* The default branch for a given repository. Most commonly this
* will be the 'master' branch but GitHub users are able to change
* their default branch in the web UI.
*/
readonly defaultBranch: Branch | null

/**
* A list of all branches (remote and local) that's currently in
* the repository.
*/
readonly allBranches: ReadonlyArray<Branch>

/**
* A list of zero to a few (at time of writing 5 but check loadRecentBranches
* in git-store for definitive answer) branches that have been checked out
* recently. This list is compiled by reading the reflog and tracking branch
* switches over the last couple of thousand reflog entries.
*/
readonly recentBranches: ReadonlyArray<Branch>
}

Expand Down
2 changes: 2 additions & 0 deletions app/src/main-process/app-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export class AppWindow {
// Disable auxclick event
// See https://developers.google.com/web/updates/2016/10/auxclick
disableBlinkFeatures: 'Auxclick',
// Enable, among other things, the ResizeObserver
experimentalFeatures: true,
},
}

Expand Down
13 changes: 11 additions & 2 deletions app/src/ui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -733,11 +733,20 @@ export class App extends React.Component<IAppProps, IAppState> {
const repository = popup.repository
const state = this.props.appStore.getRepositoryState(repository)

const tip = state.branchesState.tip
const currentBranch = tip.kind === TipState.Valid
? tip.branch
: null

return <Merge
dispatcher={this.props.dispatcher}
repository={repository}
branches={state.branchesState.allBranches}
onDismissed={this.onPopupDismissed}/>
allBranches={state.branchesState.allBranches}
defaultBranch={state.branchesState.defaultBranch}
recentBranches={state.branchesState.recentBranches}
currentBranch={currentBranch}
onDismissed={this.onPopupDismissed}
/>
}
case PopupType.RepositorySettings: {
const repository = popup.repository
Expand Down
155 changes: 155 additions & 0 deletions app/src/ui/branches/branch-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import * as React from 'react'
import { Branch } from '../../models/branch'
import { groupBranches, IBranchListItem, BranchGroupIdentifier } from './group-branches'
import { BranchListItem } from './branch'
import { FilterList, IFilterListGroup, SelectionSource } from '../lib/filter-list'
import { assertNever } from '../../lib/fatal-error'

/**
* TS can't parse generic specialization in JSX, so we have to alias it here
* with the generic type. See https://github.com/Microsoft/TypeScript/issues/6395.
*/
const BranchesFilterList: new() => FilterList<IBranchListItem> = FilterList as any

const RowHeight = 30

interface IBranchListProps {

/**
* See IBranchesState.defaultBranch
*/
readonly defaultBranch: Branch | null

/**
* The currently checked out branch or null if HEAD is detached
*/
readonly currentBranch: Branch | null

/**
* See IBranchesState.allBranches
*/
readonly allBranches: ReadonlyArray<Branch>

/**
* See IBranchesState.recentBranches
*/
readonly recentBranches: ReadonlyArray<Branch>

/**
* The currently selected branch in the list, see the onSelectionChanged prop.
*/
readonly selectedBranch: Branch | null

/**
* Called when a key down happens in the filter field. Users have a chance to
* respond or cancel the default behavior by calling `preventDefault`.
*/
readonly onFilterKeyDown?: (filter: string, event: React.KeyboardEvent<HTMLInputElement>) => void

/** Called when an item is clicked. */
readonly onItemClick?: (item: Branch) => void

/**
* This function will be called when the selection changes as a result of a
* user keyboard or mouse action (i.e. not when props change). Note that this
* differs from `onRowSelected`. For example, it won't be called if an already
* selected row is clicked on.
*
* @param selectedItem - The Branch that was just selected
* @param source - The kind of user action that provoked the change,
* either a pointer device press, or a keyboard event
* (arrow up/down)
*/
readonly onSelectionChanged?: (selectedItem: Branch | null, source: SelectionSource) => void
}

interface IBranchListState {
readonly groups: ReadonlyArray<IFilterListGroup<IBranchListItem>>
readonly selectedItem: IBranchListItem | null
}

function createState(props: IBranchListProps): IBranchListState {
const groups = groupBranches(props.defaultBranch, props.currentBranch, props.allBranches, props.recentBranches)

let selectedItem: IBranchListItem | null = null
const selectedBranch = props.selectedBranch
if (selectedBranch) {
for (const group of groups) {
selectedItem = group.items.find(i => {
const branch = i.branch
return branch.name === selectedBranch.name
}) || null

if (selectedItem) { break }
}
}

return { groups, selectedItem }
}

/** The Branches list component. */
export class BranchList extends React.Component<IBranchListProps, IBranchListState> {

public constructor(props: IBranchListProps) {
super(props)
this.state = createState(props)
}

private renderItem = (item: IBranchListItem) => {
const branch = item.branch
const commit = branch.tip
const currentBranchName = this.props.currentBranch ? this.props.currentBranch.name : null
return <BranchListItem
name={branch.name}
isCurrentBranch={branch.name === currentBranchName}
lastCommitDate={commit ? commit.author.date : null}/>
}

private getGroupLabel(identifier: BranchGroupIdentifier) {
if (identifier === 'default') {
return __DARWIN__ ? 'Default Branch' : 'Default branch'
} else if (identifier === 'recent') {
return __DARWIN__ ? 'Recent Branches' : 'Recent branches'
} else if (identifier === 'other') {
return __DARWIN__ ? 'Other Branches' : 'Other branches'
} else {
return assertNever(identifier, `Unknown identifier: ${identifier}`)
}
}

private renderGroupHeader = (identifier: BranchGroupIdentifier) => {
return <div className='branches-list-content branches-list-label'>{this.getGroupLabel(identifier)}</div>
}

private onItemClick = (item: IBranchListItem) => {
if (this.props.onItemClick) {
this.props.onItemClick(item.branch)
}
}

private onSelectionChanged = (selectedItem: IBranchListItem | null, source: SelectionSource) => {
if (this.props.onSelectionChanged) {
this.props.onSelectionChanged(selectedItem ? selectedItem.branch : null, source)
}
}

public componentWillReceiveProps(nextProps: IBranchListProps) {
this.setState(createState(nextProps))
}

public render() {
return (
<BranchesFilterList
className='branches-list'
rowHeight={RowHeight}
selectedItem={this.state.selectedItem}
renderItem={this.renderItem}
renderGroupHeader={this.renderGroupHeader}
onItemClick={this.onItemClick}
onFilterKeyDown={this.props.onFilterKeyDown}
onSelectionChanged={this.onSelectionChanged}
groups={this.state.groups}
invalidationProps={this.props.allBranches}/>
)
}
}
89 changes: 28 additions & 61 deletions app/src/ui/branches/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,7 @@ import * as React from 'react'
import { Dispatcher } from '../../lib/dispatcher'
import { Repository } from '../../models/repository'
import { Branch } from '../../models/branch'
import { groupBranches, IBranchListItem, BranchGroupIdentifier } from './group-branches'
import { BranchListItem } from './branch'
import { FilterList } from '../lib/filter-list'
import { assertNever } from '../../lib/fatal-error'

/**
* TS can't parse generic specialization in JSX, so we have to alias it here
* with the generic type. See https://github.com/Microsoft/TypeScript/issues/6395.
*/
const BranchesFilterList: new() => FilterList<IBranchListItem> = FilterList as any

const RowHeight = 30
import { BranchList } from './branch-list'

interface IBranchesProps {
readonly defaultBranch: Branch | null
Expand All @@ -24,38 +13,27 @@ interface IBranchesProps {
readonly repository: Repository
}

interface IBranchesState {
readonly selectedBranch: Branch | null
}

/** The Branches list component. */
export class Branches extends React.Component<IBranchesProps, void> {
private renderItem = (item: IBranchListItem) => {
const branch = item.branch
const commit = branch.tip
const currentBranchName = this.props.currentBranch ? this.props.currentBranch.name : null
return <BranchListItem
name={branch.name}
isCurrentBranch={branch.name === currentBranchName}
lastCommitDate={commit ? commit.author.date : null}/>
}
export class Branches extends React.Component<IBranchesProps, IBranchesState> {

private getGroupLabel(identifier: BranchGroupIdentifier) {
if (identifier === 'default') {
return 'Default Branch'
} else if (identifier === 'recent') {
return 'Recent Branches'
} else if (identifier === 'other') {
return 'Other Branches'
} else {
return assertNever(identifier, `Unknown identifier: ${identifier}`)
}
}
public constructor(props: IBranchesProps) {
super(props)

private renderGroupHeader = (identifier: BranchGroupIdentifier) => {
return <div className='branches-list-content branches-list-label'>{this.getGroupLabel(identifier)}</div>
this.state = { selectedBranch: props.currentBranch }
}

private onItemClick = (item: IBranchListItem) => {
const branch = item.branch
private onItemClick = (item: Branch) => {
this.props.dispatcher.closeFoldout()
this.props.dispatcher.checkoutBranch(this.props.repository, branch.nameWithoutRemote)

const currentBranch = this.props.currentBranch

if (!currentBranch || currentBranch.name !== item.name) {
this.props.dispatcher.checkoutBranch(this.props.repository, item.nameWithoutRemote)
}
}

private onFilterKeyDown = (filter: string, event: React.KeyboardEvent<HTMLInputElement>) => {
Expand All @@ -67,34 +45,23 @@ export class Branches extends React.Component<IBranchesProps, void> {
}
}

public render() {
const groups = groupBranches(this.props.defaultBranch, this.props.currentBranch, this.props.allBranches, this.props.recentBranches)

let selectedItem: IBranchListItem | null = null
const currentBranch = this.props.currentBranch
if (currentBranch) {
for (const group of groups) {
selectedItem = group.items.find(i => {
const branch = i.branch
return branch.name === currentBranch.name
}) || null

if (selectedItem) { break }
}
}
private onSelectionChanged = (selectedBranch: Branch) => {
this.setState({ selectedBranch })
}

public render() {
return (
<div className='branches-list-container'>
<BranchesFilterList
className='branches-list'
rowHeight={RowHeight}
selectedItem={selectedItem}
renderItem={this.renderItem}
renderGroupHeader={this.renderGroupHeader}
<BranchList
defaultBranch={this.props.defaultBranch}
currentBranch={this.props.currentBranch}
allBranches={this.props.allBranches}
recentBranches={this.props.recentBranches}
onItemClick={this.onItemClick}
onFilterKeyDown={this.onFilterKeyDown}
groups={groups}
invalidationProps={this.props.allBranches}/>
selectedBranch={this.state.selectedBranch}
onSelectionChanged={this.onSelectionChanged}
/>
</div>
)
}
Expand Down
Loading