1 | /** @jsx React.DOM */ |
---|
2 | (function() { |
---|
3 | "use strict"; |
---|
4 | |
---|
5 | window.MyAggregator = window.MyAggregator || {}; |
---|
6 | |
---|
7 | var PT = React.PropTypes; |
---|
8 | var ReactCSSTransitionGroup = window.React.addons.CSSTransitionGroup; |
---|
9 | // own components |
---|
10 | var Panel = window.MyReact.Panel; |
---|
11 | |
---|
12 | |
---|
13 | ///////////////////////////////// |
---|
14 | |
---|
15 | var SearchCorpusBox = React.createClass({displayName: 'SearchCorpusBox', |
---|
16 | propTypes: { |
---|
17 | search: PT.func.isRequired, |
---|
18 | }, |
---|
19 | |
---|
20 | getInitialState: function () { |
---|
21 | return { |
---|
22 | query: "" |
---|
23 | }; |
---|
24 | }, |
---|
25 | |
---|
26 | handleChange: function(event) { |
---|
27 | var query = event.target.value; |
---|
28 | this.setState({query: query}); |
---|
29 | |
---|
30 | if (query.length === 0 || 2 <= query.length) { |
---|
31 | this.props.search(query); |
---|
32 | } |
---|
33 | event.stopPropagation(); |
---|
34 | }, |
---|
35 | |
---|
36 | handleKey: function(event) { |
---|
37 | if (event.keyCode==13) { |
---|
38 | this.props.search(event.target.value); |
---|
39 | } |
---|
40 | }, |
---|
41 | |
---|
42 | render: function() { |
---|
43 | return React.createElement("div", {className: "form-group"}, |
---|
44 | React.createElement("input", {className: "form-control search search-collection", type: "text", |
---|
45 | value: this.state.query, placeholder: "Search for collection", |
---|
46 | onChange: this.handleChange}) |
---|
47 | ); |
---|
48 | } |
---|
49 | }); |
---|
50 | |
---|
51 | var CorpusView = window.MyAggregator.CorpusView = React.createClass({displayName: 'CorpusView', |
---|
52 | propTypes: { |
---|
53 | corpora: PT.object.isRequired, |
---|
54 | languageMap: PT.object.isRequired, |
---|
55 | }, |
---|
56 | |
---|
57 | toggleSelection: function (corpus, e) { |
---|
58 | var s = !corpus.selected; |
---|
59 | this.props.corpora.recurseCorpus(corpus, function(c) { c.selected = s; }); |
---|
60 | this.props.corpora.update(); |
---|
61 | this.stop(e); |
---|
62 | }, |
---|
63 | |
---|
64 | toggleExpansion: function (corpus) { |
---|
65 | corpus.expanded = !corpus.expanded; |
---|
66 | this.props.corpora.update(); |
---|
67 | }, |
---|
68 | |
---|
69 | selectAll: function(value) { |
---|
70 | this.props.corpora.recurse(function(c) { c.selected = value; }); |
---|
71 | this.props.corpora.update(); |
---|
72 | }, |
---|
73 | |
---|
74 | searchCorpus: function(query) { |
---|
75 | // sort fn: descending priority, stable sort |
---|
76 | var sortFn = function(a, b){ |
---|
77 | if (b.priority === a.priority) { |
---|
78 | return b.index - a.index; // stable sort |
---|
79 | } |
---|
80 | return b.priority - a.priority; |
---|
81 | }; |
---|
82 | |
---|
83 | query = query.toLowerCase(); |
---|
84 | if (!query) { |
---|
85 | this.props.corpora.recurse(function(corpus) {corpus.priority = 1; }); |
---|
86 | this.props.corpora.update(); |
---|
87 | return; |
---|
88 | } |
---|
89 | |
---|
90 | // clean up all priorities |
---|
91 | this.props.corpora.recurse(function(corpus) { |
---|
92 | corpus.priority = 0; |
---|
93 | }); |
---|
94 | |
---|
95 | // find priority for each corpus |
---|
96 | var querytokens = query.split(" ").filter(function(x){ return x.length > 0; }); |
---|
97 | this.props.corpora.recurse(function(corpus){ |
---|
98 | var title = corpus.title; |
---|
99 | querytokens.forEach(function(qtoken){ |
---|
100 | if (title && title.toLowerCase().indexOf(qtoken) >= 0) { |
---|
101 | corpus.priority ++; |
---|
102 | } |
---|
103 | if (corpus.description && corpus.description.toLowerCase().indexOf(qtoken) >= 0) { |
---|
104 | corpus.priority ++; |
---|
105 | } |
---|
106 | if (corpus.institution && corpus.institution.name && |
---|
107 | corpus.institution.name.toLowerCase().indexOf(qtoken) >= 0) { |
---|
108 | corpus.priority ++; |
---|
109 | } |
---|
110 | if (corpus.languages){ |
---|
111 | corpus.languages.forEach(function(lang){ |
---|
112 | if (lang.toLowerCase().indexOf(qtoken) >= 0){ |
---|
113 | corpus.priority ++; |
---|
114 | } |
---|
115 | }); |
---|
116 | corpus.languages.forEach(function(lang){ |
---|
117 | if (this.props.languageMap[lang].toLowerCase().indexOf(qtoken) >= 0){ |
---|
118 | corpus.priority ++; |
---|
119 | } |
---|
120 | }.bind(this)); |
---|
121 | } |
---|
122 | }.bind(this)); |
---|
123 | }.bind(this)); |
---|
124 | |
---|
125 | // ensure parents of visible corpora are also visible; maximum depth = 3 |
---|
126 | var isVisibleFn = function(corpus){ return corpus.priority > 0; }; |
---|
127 | var parentBooster = function(corpus){ |
---|
128 | if (corpus.priority <= 0 && corpus.subCorpora) { |
---|
129 | if (corpus.subCorpora.some(isVisibleFn)) { |
---|
130 | corpus.priority = 0.5; |
---|
131 | } |
---|
132 | } |
---|
133 | }; |
---|
134 | for (var i = 3; i > 0; i --) { |
---|
135 | this.props.corpora.recurse(parentBooster); |
---|
136 | } |
---|
137 | |
---|
138 | this.props.corpora.recurse(function(corpus) { corpus.subCorpora.sort(sortFn); }); |
---|
139 | this.props.corpora.corpora.sort(sortFn); |
---|
140 | |
---|
141 | // display |
---|
142 | this.props.corpora.update(); |
---|
143 | }, |
---|
144 | |
---|
145 | stop: function(e) { |
---|
146 | e.stopPropagation(); |
---|
147 | }, |
---|
148 | |
---|
149 | getMinMaxPriority: function() { |
---|
150 | var min = 1, max = 0; |
---|
151 | this.props.corpora.recurse(function(c) { |
---|
152 | if (c.priority < min) min = c.priority; |
---|
153 | if (max < c.priority) max = c.priority; |
---|
154 | }); |
---|
155 | return [min, max]; |
---|
156 | }, |
---|
157 | |
---|
158 | renderCheckbox: function(corpus) { |
---|
159 | return React.createElement("button", {className: "btn btn-default"}, |
---|
160 | corpus.selected ? |
---|
161 | React.createElement("span", {className: "glyphicon glyphicon-check", 'aria-hidden': "true"}) : |
---|
162 | React.createElement("span", {className: "glyphicon glyphicon-unchecked", 'aria-hidden': "true"}) |
---|
163 | |
---|
164 | ); |
---|
165 | }, |
---|
166 | |
---|
167 | renderExpansion: function(corpus) { |
---|
168 | if (!corpus.subCorpora || corpus.subCorpora.length === 0) { |
---|
169 | return false; |
---|
170 | } |
---|
171 | return React.createElement("div", {className: "expansion-handle", style: {}}, |
---|
172 | React.createElement("a", null, |
---|
173 | corpus.expanded ? |
---|
174 | React.createElement("span", {className: "glyphicon glyphicon-minus", 'aria-hidden': "true"}): |
---|
175 | React.createElement("span", {className: "glyphicon glyphicon-plus", 'aria-hidden': "true"}), |
---|
176 | |
---|
177 | corpus.expanded ? " Collapse ":" Expand ", " (", corpus.subCorpora.length, " subcollections)" |
---|
178 | ) |
---|
179 | ); |
---|
180 | }, |
---|
181 | |
---|
182 | renderLanguages: function(languages) { |
---|
183 | return languages |
---|
184 | .map(function(l) { return this.props.languageMap[l]; }.bind(this)) |
---|
185 | .sort() |
---|
186 | .join(", "); |
---|
187 | }, |
---|
188 | |
---|
189 | renderFilteredMessage: function() { |
---|
190 | var total = 0; |
---|
191 | var visible = 0; |
---|
192 | this.props.corpora.recurse(function(corpus){ |
---|
193 | if (corpus.visible) { |
---|
194 | total ++; |
---|
195 | if (corpus.priority > 0) { |
---|
196 | visible++; |
---|
197 | } |
---|
198 | } |
---|
199 | }); |
---|
200 | if (visible === total) { |
---|
201 | return false; |
---|
202 | } |
---|
203 | return React.createElement("div", null, " Showing ", visible, " out of ", total, " (sub)collections. "); |
---|
204 | }, |
---|
205 | |
---|
206 | renderCorpus: function(level, minmaxp, corpus) { |
---|
207 | if (!corpus.visible || corpus.priority <= 0) { |
---|
208 | return false; |
---|
209 | } |
---|
210 | |
---|
211 | var indent = {marginLeft:level*50}; |
---|
212 | var corpusContainerClass = "corpus-container "+(corpus.priority>0?"":"dimmed"); |
---|
213 | |
---|
214 | var hue = 120 * corpus.priority / minmaxp[1]; |
---|
215 | var color = minmaxp[0] === minmaxp[1] ? 'transparent' : 'hsl('+hue+', 50%, 50%)'; |
---|
216 | var priorityStyle = {paddingBottom: 4, paddingLeft: 2, borderBottom: '3px solid '+color }; |
---|
217 | var expansive = corpus.expanded ? {overflow:'hidden'} |
---|
218 | : {whiteSpace:'nowrap', overflow:'hidden', textOverflow: 'ellipsis'}; |
---|
219 | return React.createElement("div", {className: corpusContainerClass, key: corpus.title}, |
---|
220 | React.createElement("div", {className: "row corpus", onClick: this.toggleExpansion.bind(this, corpus)}, |
---|
221 | React.createElement("div", {className: "col-sm-1 vcenter"}, |
---|
222 | React.createElement("div", {className: "inline", style: priorityStyle, onClick: this.toggleSelection.bind(this,corpus)}, |
---|
223 | this.renderCheckbox(corpus) |
---|
224 | ) |
---|
225 | ), |
---|
226 | React.createElement("div", {className: "col-sm-8 vcenter"}, |
---|
227 | React.createElement("div", {style: indent}, |
---|
228 | React.createElement("h3", {style: expansive}, |
---|
229 | corpus.title, |
---|
230 | corpus.landingPage ? |
---|
231 | React.createElement("a", {href: corpus.landingPage, onClick: this.stop}, |
---|
232 | React.createElement("span", {style: {fontSize:12}}, " â Homepage "), |
---|
233 | React.createElement("i", {className: "glyphicon glyphicon-home"}) |
---|
234 | ): false |
---|
235 | ), |
---|
236 | |
---|
237 | |
---|
238 | React.createElement("p", {style: expansive}, corpus.description), |
---|
239 | this.renderExpansion(corpus) |
---|
240 | ) |
---|
241 | ), |
---|
242 | React.createElement("div", {className: "col-sm-3 vcenter"}, |
---|
243 | React.createElement("p", {style: expansive}, |
---|
244 | React.createElement("i", {className: "fa fa-institution"}), " ", corpus.institution.name |
---|
245 | ), |
---|
246 | React.createElement("p", {style: expansive}, |
---|
247 | React.createElement("i", {className: "fa fa-language"}), " ", this.renderLanguages(corpus.languages) |
---|
248 | ) |
---|
249 | ) |
---|
250 | ), |
---|
251 | corpus.expanded ? corpus.subCorpora.map(this.renderCorpus.bind(this, level+1, minmaxp)) : false |
---|
252 | ); |
---|
253 | }, |
---|
254 | |
---|
255 | render: function() { |
---|
256 | var minmaxp = this.getMinMaxPriority(); |
---|
257 | return React.createElement("div", {style: {margin: "0 30px"}}, |
---|
258 | React.createElement("div", {className: "row"}, |
---|
259 | React.createElement("div", {className: "float-left inline"}, |
---|
260 | React.createElement("h3", {style: {marginTop:10}}, |
---|
261 | this.props.corpora.getSelectedMessage() |
---|
262 | ) |
---|
263 | ), |
---|
264 | React.createElement("div", {className: "float-right inline"}, |
---|
265 | React.createElement("button", {className: "btn btn-default", style: { marginRight: 10}, onClick: this.selectAll.bind(this,true)}, |
---|
266 | " Select all"), |
---|
267 | React.createElement("button", {className: "btn btn-default", style: { marginRight: 20}, onClick: this.selectAll.bind(this,false)}, |
---|
268 | " Deselect all") |
---|
269 | ), |
---|
270 | React.createElement("div", {className: "float-right inline"}, |
---|
271 | React.createElement("div", {className: "inline", style: { marginRight: 20}}, |
---|
272 | React.createElement(SearchCorpusBox, {search: this.searchCorpus}), |
---|
273 | this.renderFilteredMessage() |
---|
274 | ) |
---|
275 | ) |
---|
276 | ), |
---|
277 | |
---|
278 | this.props.corpora.corpora.map(this.renderCorpus.bind(this, 0, minmaxp)) |
---|
279 | ); |
---|
280 | } |
---|
281 | }); |
---|
282 | |
---|
283 | })(); |
---|