Skip to content
This repository was archived by the owner on Nov 8, 2022. It is now read-only.

Commit a5274a8

Browse files
committed
basic github repo searcher with Rx.js & Ramda.js. refactor later
1 parent 7e5146a commit a5274a8

File tree

13 files changed

+270
-30
lines changed

13 files changed

+270
-30
lines changed

containers/Body/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const selector = ({ store }) => ({
4848
})
4949

5050
class ContentContainer extends React.Component {
51-
componentDidMount() {
51+
componentWillMount() {
5252
logic.init(this.props.body)
5353
debug('init')
5454
}

containers/UniversePanel/Modal.js

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import styled from 'styled-components'
22

3+
import Img from '../../components/Img'
4+
35
// center css see: https://stackoverflow.com/questions/1776915/how-to-center-absolutely-positioned-element-in-div
46
// flex-grow example: http://zhoon.github.io/css3/2014/08/23/flex.html
57
export const PanelContainer = styled.div`
68
position: absolute;
7-
top: 20vh;
9+
top: 10vh;
810
left: 50%;
911
`
1012

@@ -13,10 +15,11 @@ export const Wraper = styled.div`
1315
left: -50%;
1416
display: flex;
1517
flex-direction: column;
16-
justify-content: center;
18+
height: 65vh;
19+
overflow: scroll;
1720
`
1821

19-
export const InfoBar = styled.div`
22+
export const BaseBar = styled.div`
2023
border: 1px solid white;
2124
box-shadow: 0 12px 48px 0 rgba(0, 0, 0, 0.4);
2225
border: 1px solid #014354;
@@ -28,6 +31,14 @@ export const InfoBar = styled.div`
2831
display: flex;
2932
flex-direction: row;
3033
`
34+
export const EditorBar = styled(BaseBar)`
35+
position: relative;
36+
left: -50%;
37+
`
38+
export const InfoBar = styled(BaseBar)`
39+
padding: 10px;
40+
min-height: 100px;
41+
`
3142

3243
export const AddOn = styled.div`
3344
transform: rotate(-45deg);
@@ -58,3 +69,50 @@ export const InputBar = styled.input`
5869
border-radius: 0;
5970
transition: all 400ms ease;
6071
`
72+
73+
export const AvatarWrapper = styled.div`
74+
width: 10%;
75+
margin-right: 10px;
76+
`
77+
78+
export const AvatarImg = styled(Img)`
79+
width: 100%;
80+
border-radius: 50%;
81+
`
82+
export const ContentWraper = styled.div`
83+
color: tomato;
84+
text-align: left;
85+
flex-grow: 1;
86+
`
87+
export const Title = styled.div`color: white;`
88+
export const Desc = styled.div`
89+
color: #4c7c8a;
90+
text-overflow: ellipsis;
91+
width: 400px;
92+
white-space: nowrap;
93+
overflow: hidden;
94+
margin-bottom: 7px;
95+
`
96+
97+
export const SubInfoWraper = styled.div`
98+
display: flex;
99+
justify-content: space-between;
100+
`
101+
102+
export const RepoLang = styled.div`
103+
color: #4c7c8a;
104+
font-style: italic;
105+
`
106+
107+
export const RepoStar = styled.div`
108+
color: #4c7c8a;
109+
font-style: italic;
110+
margin-right: 10px;
111+
`
112+
113+
/*
114+
text-overflow: ellipsis;
115+
width: 400px;
116+
white-space: nowrap;
117+
overflow: hidden;
118+
*/

containers/UniversePanel/Pigeon.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Observable } from 'rxjs/Observable'
2+
import { Subject } from 'rxjs/Subject'
3+
4+
import 'rxjs/add/observable/of'
5+
import 'rxjs/add/observable/fromPromise'
6+
7+
import 'rxjs/add/operator/do'
8+
import 'rxjs/add/operator/catch'
9+
import 'rxjs/add/operator/switchMap'
10+
import 'rxjs/add/operator/debounceTime'
11+
import 'rxjs/add/operator/distinctUntilChanged'
12+
import 'rxjs/add/operator/map'
13+
import 'rxjs/add/operator/filter'
14+
15+
import R from 'ramda'
16+
import fetch from 'isomorphic-fetch'
17+
18+
export const fuck = 1
19+
const QUERY_REPOS =
20+
'https://api.github.com/search/repositories?sort=stars&order=desc&q='
21+
22+
const getReposPromise = query => {
23+
const url = `${QUERY_REPOS}${query}`
24+
return fetch(url).then(res => res.json())
25+
}
26+
27+
const getRepos = query => {
28+
const promise = getReposPromise(query)
29+
return Observable.fromPromise(promise)
30+
}
31+
32+
const getGithubRepos = R.curry(getRepos)
33+
34+
const isEmptyValue = R.compose(R.isEmpty, R.trim)
35+
const isNotEmptyValue = R.compose(R.not, isEmptyValue)
36+
37+
export class SearchService {
38+
constructor() {
39+
this.searchTerm = new Subject()
40+
// this.doSearch = R.curry(this.githubQuery)
41+
}
42+
43+
search(term) {
44+
this.searchTerm.next(term)
45+
}
46+
47+
get() {
48+
return (
49+
this.searchTerm
50+
.debounceTime(400)
51+
.filter(isNotEmptyValue)
52+
// .do(value => console.log('after:', value))
53+
.distinctUntilChanged()
54+
.switchMap(getGithubRepos)
55+
.catch(error => {
56+
console.error(error)
57+
return Observable.of([])
58+
})
59+
)
60+
}
61+
62+
emptyInput() {
63+
return this.searchTerm.debounceTime(600).filter(isEmptyValue)
64+
}
65+
}

containers/UniversePanel/index.js

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,54 +10,84 @@ import { inject, observer } from 'mobx-react'
1010
// import Link from 'next/link'
1111
// import styled from 'styled-components'
1212

13+
import A from '../../components/A'
1314
import { makeDebugger } from '../../utils/debug'
1415
import * as logic from './logic'
1516

16-
import { PanelContainer, InfoBar, Wraper, InputBar, AddOn } from './Modal'
17+
import {
18+
PanelContainer,
19+
InfoBar,
20+
EditorBar,
21+
Wraper,
22+
InputBar,
23+
AddOn,
24+
AvatarImg,
25+
AvatarWrapper,
26+
ContentWraper,
27+
Title,
28+
Desc,
29+
RepoLang,
30+
RepoStar,
31+
SubInfoWraper,
32+
} from './Modal'
1733

1834
const debug = makeDebugger('C:UniversePanel')
1935

36+
function inputChange(e) {
37+
logic.search(e.target.value)
38+
}
39+
2040
const SearchEditor = () => (
21-
<InfoBar>
41+
<EditorBar>
2242
<AddOn>&#9906;</AddOn>
2343
<InputBar
2444
spellCheck={false}
2545
autoCapitalize={false}
2646
autoCorrect="off"
2747
autoComplete="off"
48+
onChange={inputChange}
2849
/>
29-
</InfoBar>
50+
</EditorBar>
3051
)
3152

3253
const selector = ({ store }) => ({
33-
store: store.sidebar,
54+
store: store.github,
3455
})
3556

3657
class UniversePanelContainer extends React.Component {
37-
componentDidMount() {
58+
// TODO use componentWillMount?
59+
componentWillMount() {
3860
debug('mount')
3961
logic.init(this.props.store)
4062
}
4163

4264
render() {
43-
/*
44-
<PanelContainer>
45-
<Wraper>
46-
<InfoBar>
47-
<SearchEditor ... />
48-
</InfoBar>
49-
50-
</Wraper>
51-
</PanelContainer>
52-
53-
54-
*/
65+
const repos = this.props.store.githubRepos
66+
debug('repos: ', repos.toJSON())
5567

5668
return (
5769
<PanelContainer>
70+
<SearchEditor />
5871
<Wraper>
59-
<SearchEditor />
60-
<InfoBar>UniversePanel container!</InfoBar>
72+
{repos.map(repo => (
73+
<InfoBar key={repo.id}>
74+
<AvatarWrapper onClick={logic.watshData}>
75+
<AvatarImg src={repo.owner.avatar_url} alt="repo avatar" />
76+
</AvatarWrapper>
77+
<ContentWraper>
78+
<Title>
79+
<A href={repo.owner.html_url}>
80+
{repo.owner.login} / {repo.name}
81+
</A>
82+
</Title>
83+
<Desc>{repo.description}</Desc>
84+
<SubInfoWraper>
85+
<RepoLang>{repo.language}</RepoLang>
86+
<RepoStar>★&nbsp;{repo.stargazers_count}</RepoStar>
87+
</SubInfoWraper>
88+
</ContentWraper>
89+
</InfoBar>
90+
))}
6191
</Wraper>
6292
</PanelContainer>
6393
)

containers/UniversePanel/logic.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,39 @@
1+
import R from 'ramda'
2+
13
import { makeDebugger } from '../../utils/debug'
4+
import { SearchService } from './Pigeon'
25

36
const debug = makeDebugger('L:UniversePanel')
47

58
let store = null
9+
let Pigeon = null
10+
11+
const repoData = R.map(
12+
R.pick(['id', 'name', 'description', 'language', 'owner', 'stargazers_count'])
13+
)
14+
15+
export function watshData() {
16+
debug('watshData')
17+
}
618

7-
export function otherLogic() {
8-
return 'otherLogic'
19+
export function search(val) {
20+
// console.log('search: ', val)
21+
Pigeon.search(val)
922
}
1023

1124
export function init(selectedStore) {
1225
debug('store', store)
1326
store = selectedStore
27+
Pigeon = new SearchService()
28+
29+
Pigeon.get().subscribe(res => {
30+
debug('Pigeon get: ', res)
31+
// debug('washed: ', repoData(res.items))
32+
store.replaceRepos(repoData(res.items))
33+
})
34+
35+
Pigeon.emptyInput().subscribe(() => {
36+
debug('Pigeon get emptyInput!')
37+
store.clearRepos()
38+
})
1439
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"react-icons": "^2.2.5",
9797
"react-intl": "^2.3.0",
9898
"react-world-flags": "^0.0.3",
99+
"rxjs": "^5.4.3",
99100
"styled-components": "2.1.2",
100101
"timeago-react": "^1.2.2"
101102
},

stores/AppStore/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { globalThemes, themeNames } from '../../utils/themes'
99

1010
import SidebarStore from '../SidebarStore'
1111
import BodyStore from '../BodyStore'
12+
import GithubEampleStore from '../GithubEampleStore'
1213

1314
const debug = makeDebugger('S:AppStore')
1415

@@ -18,6 +19,7 @@ const AppStore = t
1819
// header: t...,
1920
// banner: t...,
2021
body: t.optional(BodyStore, {}),
22+
github: t.optional(GithubEampleStore, {}),
2123
/* account: t..., */
2224
appTheme: t.optional(t.enumeration('theme', themeNames), 'default'),
2325
appLocale: t.optional(t.enumeration('locale', ['zh', 'en']), 'zh'),

stores/GithubEampleStore/index.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* GithubEampleStore store
3+
*
4+
*/
5+
6+
import { types as t, getParent } from 'mobx-state-tree'
7+
// import { makeDebugger } from '../../utils/debug'
8+
9+
// const debug = makeDebugger('S:GithubEampleStore')
10+
11+
const RepoOwner = t.model('RepoOwner', {
12+
login: t.string,
13+
avatar_url: t.string,
14+
html_url: t.string,
15+
})
16+
17+
const Repo = t.model('Repo', {
18+
id: t.number,
19+
description: t.string,
20+
language: t.maybe(t.string), // language maybe null, like awesome-react
21+
stargazers_count: t.number,
22+
name: t.string,
23+
owner: RepoOwner,
24+
})
25+
26+
const GithubEampleStore = t
27+
.model('GithubEampleStore', {
28+
repos: t.optional(t.array(Repo), []),
29+
})
30+
.views(self => ({
31+
get app() {
32+
return getParent(self)
33+
},
34+
get githubRepos() {
35+
return self.repos
36+
},
37+
}))
38+
.actions(self => ({
39+
replaceRepos(data) {
40+
// debug('replaceRepos data: ', data)
41+
self.repos = data
42+
},
43+
clearRepos() {
44+
self.repos = []
45+
},
46+
}))
47+
48+
export default GithubEampleStore
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* GithubEampleStore store test
3+
*
4+
*/
5+
6+
// import GithubEampleStore from '../index'
7+
8+
it('1 + 1 = 2', () => {
9+
expect(1 + 1).toBe(2)
10+
})

0 commit comments

Comments
 (0)