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