mirror of
https://github.com/desktop/desktop
synced 2024-11-05 20:49:32 +00:00
commit
9c8ffbe8a5
6 changed files with 194 additions and 130 deletions
68
src/app.tsx
68
src/app.tsx
|
@ -1,12 +1,16 @@
|
|||
import * as React from 'react'
|
||||
import ThingList from './thing-list'
|
||||
import ReposList from './repos-list'
|
||||
import Info from './info'
|
||||
import UsersStore from './users-store'
|
||||
import User from './user'
|
||||
import NotLoggedIn from './not-logged-in'
|
||||
import API from './lib/api'
|
||||
import {Repo} from './lib/api'
|
||||
|
||||
interface AppState {
|
||||
selectedRow: number,
|
||||
repos: Repo[],
|
||||
loadingRepos: boolean,
|
||||
user: User
|
||||
}
|
||||
|
||||
|
@ -27,14 +31,43 @@ const ContentStyle = {
|
|||
}
|
||||
|
||||
export default class App extends React.Component<AppProps, AppState> {
|
||||
private api: API
|
||||
|
||||
public constructor(props: AppProps) {
|
||||
super(props)
|
||||
|
||||
props.usersStore.onUsersChanged(users => {
|
||||
this.setState({selectedRow: this.state.selectedRow, user: users[0]})
|
||||
const user = users[0]
|
||||
this.api = new API(user)
|
||||
this.setState(Object.assign({}, this.state, {user}))
|
||||
this.fetchRepos()
|
||||
})
|
||||
|
||||
this.state = {selectedRow: -1, user: props.usersStore.getUsers()[0]}
|
||||
const user = props.usersStore.getUsers()[0]
|
||||
this.state = {
|
||||
selectedRow: -1,
|
||||
user,
|
||||
loadingRepos: true,
|
||||
repos: []
|
||||
}
|
||||
|
||||
if (user) {
|
||||
this.api = new API(user)
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchRepos() {
|
||||
const repos = await this.api.fetchRepos()
|
||||
this.setState(Object.assign({}, this.state, {
|
||||
loadingRepos: false,
|
||||
repos
|
||||
}))
|
||||
}
|
||||
|
||||
public async componentWillMount() {
|
||||
if (this.api) {
|
||||
this.fetchRepos()
|
||||
}
|
||||
}
|
||||
|
||||
private renderTitlebar() {
|
||||
|
@ -52,19 +85,38 @@ export default class App extends React.Component<AppProps, AppState> {
|
|||
)
|
||||
}
|
||||
|
||||
private renderApp() {
|
||||
const selectedRepo = this.state.repos[this.state.selectedRow]
|
||||
return (
|
||||
<div style={ContentStyle}>
|
||||
<ReposList selectedRow={this.state.selectedRow}
|
||||
onSelectionChanged={row => this.handleSelectionChanged(row)}
|
||||
user={this.state.user}
|
||||
repos={this.state.repos}
|
||||
loading={this.state.loadingRepos}/>
|
||||
<Info selectedRepo={selectedRepo} user={this.state.user}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private renderNotLoggedIn() {
|
||||
return (
|
||||
<div style={ContentStyle}>
|
||||
<NotLoggedIn/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div style={AppStyle}>
|
||||
{this.renderTitlebar()}
|
||||
<div style={ContentStyle}>
|
||||
<ThingList selectedRow={this.state.selectedRow} onSelectionChanged={row => this.handleSelectionChanged(row)}/>
|
||||
{this.state.user ? <Info selectedRow={this.state.selectedRow} user={this.state.user}/> : <NotLoggedIn/>}
|
||||
</div>
|
||||
{this.state.user ? this.renderApp() : this.renderNotLoggedIn()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private handleSelectionChanged(row: number) {
|
||||
this.setState({selectedRow: row, user: this.state.user})
|
||||
this.setState(Object.assign({}, this.state, {selectedRow: row}))
|
||||
}
|
||||
}
|
||||
|
|
22
src/auth.ts
22
src/auth.ts
|
@ -20,7 +20,8 @@ interface AuthState {
|
|||
let authState: AuthState = null
|
||||
|
||||
export async function requestToken(code: string): Promise<string> {
|
||||
const response = await fetch(`${authState.endpoint}/login/oauth/access_token`, {
|
||||
const urlBase = getOAuthURL(authState.endpoint)
|
||||
const response = await fetch(`${urlBase}/login/oauth/access_token`, {
|
||||
method: 'post',
|
||||
headers: DefaultHeaders,
|
||||
body: JSON.stringify({
|
||||
|
@ -34,18 +35,29 @@ export async function requestToken(code: string): Promise<string> {
|
|||
return json.access_token
|
||||
}
|
||||
|
||||
function getOAuthURL(authState: AuthState): string {
|
||||
return `${authState.endpoint}/login/oauth/authorize?client_id=${ClientID}&scope=repo&state=${authState.oAuthState}`
|
||||
function getOAuthAuthorizationURL(authState: AuthState): string {
|
||||
const urlBase = getOAuthURL(authState.endpoint)
|
||||
return `${urlBase}/login/oauth/authorize?client_id=${ClientID}&scope=repo&state=${authState.oAuthState}`
|
||||
}
|
||||
|
||||
function getOAuthURL(endpoint: string): string {
|
||||
if (endpoint === getDotComEndpoint()) {
|
||||
// GitHub.com is A Special Snowflake in that the API lives at a subdomain
|
||||
// but OAuth lives on the parent domain.
|
||||
return 'https://github.com'
|
||||
} else {
|
||||
return endpoint
|
||||
}
|
||||
}
|
||||
|
||||
export function getDotComEndpoint(): string {
|
||||
return 'https://github.com'
|
||||
return 'https://api.github.com'
|
||||
}
|
||||
|
||||
export function askUserToAuth(endpoint: string) {
|
||||
authState = {oAuthState: guid(), endpoint}
|
||||
|
||||
shell.openExternal(getOAuthURL(authState))
|
||||
shell.openExternal(getOAuthAuthorizationURL(authState))
|
||||
}
|
||||
|
||||
export function getKeyForUser(user: User): string {
|
||||
|
|
77
src/info.tsx
77
src/info.tsx
|
@ -1,24 +1,16 @@
|
|||
import {shell} from 'electron'
|
||||
import * as React from 'react'
|
||||
|
||||
import User from './user'
|
||||
|
||||
const Octokat = require('octokat')
|
||||
|
||||
const LOLZ = [
|
||||
'http://www.reactiongifs.com/r/drkrm.gif',
|
||||
'http://www.reactiongifs.com/r/wvy1.gif',
|
||||
'http://www.reactiongifs.com/r/ihniwid.gif',
|
||||
'http://www.reactiongifs.com/r/dTa.gif',
|
||||
'http://www.reactiongifs.com/r/didit.gif'
|
||||
]
|
||||
import {Repo} from './lib/api'
|
||||
|
||||
interface InfoProps {
|
||||
selectedRow: number,
|
||||
selectedRepo: Repo,
|
||||
user: User
|
||||
}
|
||||
|
||||
interface InfoState {
|
||||
userAvatarURL: string
|
||||
|
||||
}
|
||||
|
||||
const ContainerStyle = {
|
||||
|
@ -27,77 +19,26 @@ const ContainerStyle = {
|
|||
flex: 1
|
||||
}
|
||||
|
||||
const ImageStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flex: 1
|
||||
}
|
||||
|
||||
const AvatarStyle = {
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: '50%',
|
||||
paddingRight: 4
|
||||
}
|
||||
|
||||
export default class Info extends React.Component<InfoProps, InfoState> {
|
||||
public constructor() {
|
||||
super()
|
||||
|
||||
this.state = {userAvatarURL: ''}
|
||||
}
|
||||
|
||||
public async componentWillMount() {
|
||||
if (!this.props.user) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const api = new Octokat({token: this.props.user.getToken()})
|
||||
const user = await api.user.fetch()
|
||||
this.setState({userAvatarURL: user.avatarUrl})
|
||||
console.log('user', user)
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
private renderNoSelection() {
|
||||
return (
|
||||
<div>
|
||||
<div>No row selected!</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private renderUser() {
|
||||
return (
|
||||
<div>
|
||||
<img style={AvatarStyle} src={this.state.userAvatarURL}/>
|
||||
<div>No repo selected!</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
public render() {
|
||||
const row = this.props.selectedRow
|
||||
if (row < 0) {
|
||||
const repo = this.props.selectedRepo
|
||||
if (!repo) {
|
||||
return this.renderNoSelection()
|
||||
}
|
||||
|
||||
const img = LOLZ[row % LOLZ.length]
|
||||
return (
|
||||
<div style={ContainerStyle}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}>
|
||||
{this.renderUser()}
|
||||
<div>Row {row + 1} is selected!</div>
|
||||
</div>
|
||||
Stars: {repo.stargazersCount}
|
||||
|
||||
<div style={ImageStyle}>
|
||||
<img src={img}/>
|
||||
</div>
|
||||
<button onClick={() => shell.openExternal(repo.htmlUrl)}>Open</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
36
src/lib/api.ts
Normal file
36
src/lib/api.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import User from '../user'
|
||||
|
||||
const Octokat = require('octokat')
|
||||
|
||||
export interface Repo {
|
||||
cloneUrl: string,
|
||||
htmlUrl: string,
|
||||
name: string
|
||||
owner: {
|
||||
avatarUrl: string,
|
||||
login: string
|
||||
type: 'user' | 'org'
|
||||
},
|
||||
private: boolean,
|
||||
stargazersCount: number
|
||||
}
|
||||
|
||||
export default class API {
|
||||
private client: any
|
||||
|
||||
public constructor(user: User) {
|
||||
this.client = new Octokat({token: user.getToken(), rootURL: user.getEndpoint()})
|
||||
}
|
||||
|
||||
public async fetchRepos(): Promise<Repo[]> {
|
||||
const results: Repo[] = []
|
||||
let nextPage = this.client.user.repos
|
||||
while (nextPage) {
|
||||
const request = await nextPage.fetch()
|
||||
results.push(...request.items)
|
||||
nextPage = request.nextPage
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
}
|
72
src/repos-list.tsx
Normal file
72
src/repos-list.tsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import List from './list'
|
||||
import User from './user'
|
||||
import {Repo} from './lib/api'
|
||||
|
||||
interface ReposListProps {
|
||||
selectedRow: number,
|
||||
onSelectionChanged: (row: number) => void,
|
||||
user: User,
|
||||
loading: boolean,
|
||||
repos: Repo[]
|
||||
}
|
||||
|
||||
const RowHeight = 44
|
||||
|
||||
export default class ReposList extends React.Component<ReposListProps, void> {
|
||||
private renderRow(row: number): JSX.Element {
|
||||
const selected = row === this.props.selectedRow
|
||||
const repo = this.props.repos[row]
|
||||
const rowStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: 4,
|
||||
backgroundColor: selected ? 'blue' : 'white',
|
||||
color: selected ? 'white' : 'black',
|
||||
height: RowHeight
|
||||
}
|
||||
|
||||
const titleStyle = {
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
|
||||
const whiteness = 140
|
||||
const ownerStyle = {
|
||||
fontSize: '0.8em',
|
||||
color: selected ? 'white' : `rgba(${whiteness}, ${whiteness}, ${whiteness}, 1)`
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={rowStyle} key={row.toString()}>
|
||||
<div style={titleStyle} title={repo.name}>{repo.name}</div>
|
||||
<div style={ownerStyle}>
|
||||
by {repo.owner.login} <img src={repo.owner.avatarUrl} style={{width: 12, height: 12, borderRadius: '50%'}}/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private renderLoading() {
|
||||
return (
|
||||
<div>Loading…</div>
|
||||
)
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.props.loading) {
|
||||
return this.renderLoading()
|
||||
}
|
||||
|
||||
return (
|
||||
<List itemCount={this.props.repos.length}
|
||||
itemHeight={RowHeight}
|
||||
renderItem={row => this.renderRow(row)}
|
||||
selectedRow={this.props.selectedRow}
|
||||
onSelectionChanged={row => this.props.onSelectionChanged(row)}
|
||||
style={{width: 120}}/>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
import * as React from 'react'
|
||||
|
||||
import List from './list'
|
||||
|
||||
type ThingListProps = {
|
||||
selectedRow: number,
|
||||
onSelectionChanged: (row: number) => void
|
||||
}
|
||||
|
||||
const RowHeight = 44
|
||||
|
||||
export default class ThingList extends React.Component<ThingListProps, void> {
|
||||
public constructor(props: ThingListProps) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
private renderRow(row: number): JSX.Element {
|
||||
const selected = row === this.props.selectedRow
|
||||
const inlineStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: 4,
|
||||
backgroundColor: selected ? 'blue' : 'white',
|
||||
color: selected ? 'white' : 'black',
|
||||
height: RowHeight
|
||||
}
|
||||
const whiteness = 140
|
||||
return (
|
||||
<div style={inlineStyle} key={row.toString()}>
|
||||
<div>Item {row + 1}</div>
|
||||
<div style={{
|
||||
fontSize: '0.8em',
|
||||
color: selected ? 'white' : `rgba(${whiteness}, ${whiteness}, ${whiteness}, 1)`
|
||||
}}>Some subtitle</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<List itemCount={10000}
|
||||
itemHeight={RowHeight}
|
||||
renderItem={row => this.renderRow(row)}
|
||||
selectedRow={this.props.selectedRow}
|
||||
onSelectionChanged={row => this.props.onSelectionChanged(row)}
|
||||
style={{width: 120}}/>
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue