OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /** The top-level collection of all sections for a user. */ | |
6 // TODO(jimhug): This is known as UserData in the server model. | |
7 class Sections implements Collection<Section> { | |
8 final List<Section> _sections; | |
9 | |
10 Sections(this._sections); | |
11 | |
12 operator [](int i) => _sections[i]; | |
13 | |
14 int get length() => _sections.length; | |
15 | |
16 List<String> get sectionTitles() => | |
17 CollectionUtils.map(_sections, (s) => s.title); | |
18 | |
19 void refresh() { | |
20 // TODO(jimhug): http://b/issue?id=5351067 | |
21 } | |
22 | |
23 /** | |
24 * Find the Section object that has a given title. | |
25 * This is used to integrate well with [ConveyorView]. | |
26 */ | |
27 Section findSection(String name) { | |
28 return CollectionUtils.find(_sections, (sect) => sect.title == name); | |
29 } | |
30 | |
31 // TODO(jimhug): Track down callers! | |
32 Iterator<Section> iterator() => _sections.iterator(); | |
33 | |
34 // TODO(jimhug): Better support for switching between local dev and server. | |
35 static bool get runningFromFile() { | |
36 return window.location.protocol.startsWith('file:'); | |
37 } | |
38 | |
39 static String get home() { | |
40 // TODO(jmesserly): window.location.origin not available on Safari 4. | |
41 // Move this workaround to the DOM code. See bug 5389503. | |
42 return window.location.protocol + '//' + window.location.host; | |
43 } | |
44 | |
45 // This method is exposed for tests. | |
46 static void initializeFromData(String data, void callback(Sections sects)) { | |
47 final decoder = new Decoder(data); | |
48 int nSections = decoder.readInt(); | |
49 final sections = new List<Section>(); | |
50 | |
51 for (int i=0; i < nSections; i++) { | |
52 sections.add(Section.decode(decoder)); | |
53 } | |
54 callback(new Sections(sections)); | |
55 } | |
56 | |
57 static void initializeFromUrl(void callback(Sections sections)) { | |
58 if (Sections.runningFromFile) { | |
59 initializeFromData(CannedData.data['user.data'], callback); | |
60 } else { | |
61 // TODO(jmesserly): display an error if we fail here! Silent failure bad. | |
62 new XMLHttpRequest.getTEMPNAME('$home/data/user.data', | |
63 EventBatch.wrap((request) { | |
64 // TODO(jimhug): Nice response if get error back from server. | |
65 // TODO(jimhug): Might be more efficient to parse request in sections. | |
66 initializeFromData(request.responseText, callback); | |
67 })); | |
68 } | |
69 } | |
70 | |
71 Section findSectionById(String id) { | |
72 return CollectionUtils.find(_sections, (section) => section.id == id); | |
73 } | |
74 | |
75 /** | |
76 * Given the name of a section, find its index in the set. | |
77 */ | |
78 int findSectionIndex(String name) { | |
79 for (int i = 0; i < _sections.length; i++) { | |
80 if (name == _sections[i].title) { | |
81 return i; | |
82 } | |
83 } | |
84 return -1; | |
85 } | |
86 | |
87 List<Section> get sections() => _sections; | |
88 | |
89 // Collection<Section> methods: | |
90 List map(f(Section element)) { | |
91 return Collections.map(this, new List(), f); | |
92 } | |
93 | |
94 List<Section> filter(bool f(Section element)) { | |
95 return Collections.filter(this, new List<Section>(), f); | |
96 } | |
97 bool every(bool f(Section element)) => Collections.every(this, f); | |
98 bool some(bool f(Section element)) => Collections.some(this, f); | |
99 void forEach(void f(Section element)) { Collections.forEach(this, f); } | |
100 | |
101 // TODO(jmesserly): this should be a property | |
102 bool isEmpty() => length == 0; | |
103 } | |
104 | |
105 | |
106 /** A collection of data sources representing a page in the UI. */ | |
107 class Section { | |
108 final String id; | |
109 final String title; | |
110 ObservableList<Feed> feeds; | |
111 | |
112 // Public for testing. TODO(jacobr): find a cleaner solution. | |
113 Section(this.id, this.title, this.feeds); | |
114 | |
115 void refresh() { | |
116 for (final feed in feeds) { | |
117 // TODO(jimhug): http://b/issue?id=5351067 | |
118 } | |
119 } | |
120 | |
121 static Section decode(Decoder decoder) { | |
122 final sectionId = decoder.readString(); | |
123 final sectionTitle = decoder.readString(); | |
124 | |
125 final nSources = decoder.readInt(); | |
126 final feeds = new ObservableList<Feed>(); | |
127 for (int j=0; j < nSources; j++) { | |
128 feeds.add(Feed.decode(decoder)); | |
129 } | |
130 return new Section(sectionId, sectionTitle, feeds); | |
131 } | |
132 | |
133 Feed findFeed(String id_) { | |
134 return CollectionUtils.find(feeds, (feed) => feed.id == id_); | |
135 } | |
136 } | |
137 | |
138 /** Provider of a news feed. */ | |
139 class Feed { | |
140 String id; | |
141 final String title; | |
142 final String iconUrl; | |
143 final String description; | |
144 ObservableList<Article> articles; | |
145 ObservableValue<bool> error; // TODO(jimhug): Check if dead code. | |
146 | |
147 Feed(this.id, this.title, this.iconUrl, [this.description = '']) | |
148 : articles = new ObservableList<Article>(), | |
149 error = new ObservableValue<bool>(false); | |
150 | |
151 static Feed decode(Decoder decoder) { | |
152 final sourceId = decoder.readString(); | |
153 final sourceTitle = decoder.readString(); | |
154 final sourceIcon = decoder.readString(); | |
155 final feed = new Feed(sourceId, sourceTitle, sourceIcon); | |
156 final nItems = decoder.readInt(); | |
157 | |
158 for (int i=0; i < nItems; i++) { | |
159 feed.articles.add(Article.decodeHeader(feed, decoder)); | |
160 } | |
161 return feed; | |
162 } | |
163 | |
164 Article findArticle(String id_) { | |
165 return CollectionUtils.find(articles, (article) => article.id == id_); | |
166 } | |
167 | |
168 void refresh() {} | |
169 } | |
170 | |
171 | |
172 /** A single article or posting to display. */ | |
173 class Article { | |
174 final String id; | |
175 Date date; | |
176 final String title; | |
177 final String author; | |
178 final bool hasThumbnail; | |
179 String textBody; // TODO(jimhug): rename to snipppet. | |
180 final Feed dataSource; // TODO(jimhug): rename to feed. | |
181 String _htmlBody; | |
182 String srcUrl; | |
183 final ObservableValue<bool> unread; // TODO(jimhug): persist to server. | |
184 | |
185 bool error; // TODO(jimhug): Check if this is dead and remove. | |
186 | |
187 Article(this.dataSource, this.id, this.date, this.title, this.author, | |
188 this.srcUrl, this.hasThumbnail, this.textBody, | |
189 [this._htmlBody = null, bool unread = true, this.error = false]) | |
190 : unread = new ObservableValue<bool>(unread); | |
191 | |
192 String get htmlBody() { | |
193 _ensureLoaded(); | |
194 return _htmlBody; | |
195 } | |
196 | |
197 String get dataUri() { | |
198 return Uri.encodeComponent(id).replaceAll( | |
199 ',', '%2C').replaceAll('%2F', '/'); | |
200 } | |
201 | |
202 String get thumbUrl() { | |
203 if (!hasThumbnail) return null; | |
204 | |
205 var home; | |
206 if (Sections.runningFromFile) { | |
207 home = 'http://dart.googleplex.com'; | |
208 } else { | |
209 home = Sections.home; | |
210 } | |
211 // By default images from the real server are cached. | |
212 // Bump the version flag if you change the thumbnail size, and you want to | |
213 // get the new images. Our server ignores the query params but it gets | |
214 // around appengine server side caching and the client side cache. | |
215 return '$home/data/$dataUri.jpg?v=0'; | |
216 } | |
217 | |
218 // TODO(jimhug): need to return a lazy Observable<String> and also | |
219 // add support for preloading. | |
220 void _ensureLoaded() { | |
221 if (_htmlBody !== null) return; | |
222 | |
223 var name = '$dataUri.html'; | |
224 if (Sections.runningFromFile) { | |
225 _htmlBody = CannedData.data[name]; | |
226 } else { | |
227 // TODO(jimhug): Remove this truly evil synchronoush xhr. | |
228 final req = new XMLHttpRequest(); | |
229 req.open('GET', '${Sections.home}/data/$name', false); | |
230 req.send(); | |
231 _htmlBody = req.responseText; | |
232 } | |
233 } | |
234 | |
235 static Article decodeHeader(Feed source, Decoder decoder) { | |
236 final id = decoder.readString(); | |
237 final title = decoder.readString(); | |
238 final srcUrl = decoder.readString(); | |
239 final hasThumbnail = decoder.readBool(); | |
240 final author = decoder.readString(); | |
241 final dateInSeconds = decoder.readInt(); | |
242 final snippet = decoder.readString(); | |
243 final date = new Date.fromEpoch(dateInSeconds*1000, new TimeZone.utc()); | |
244 return new Article(source, id, date, title, author, srcUrl, hasThumbnail, | |
245 snippet); | |
246 } | |
247 } | |
OLD | NEW |