| OLD | NEW |
| (Empty) |
| 1 <!-- | |
| 2 Copyright 2016 The LUCI Authors. All rights reserved. | |
| 3 Use of this source code is governed under the Apache License, Version 2.0 | |
| 4 that can be found in the LICENSE file. | |
| 5 --> | |
| 6 | |
| 7 <link rel="import" href="../bower_components/polymer/polymer.html"> | |
| 8 <link rel="import" href="../bower_components/iron-icon/iron-icon.html"> | |
| 9 <link rel="import" href="../bower_components/paper-material/paper-material.html"
> | |
| 10 <link rel="import" href="../logdog-styles/app-theme.html"> | |
| 11 <link rel="import" href="../rpc/rpc-client.html"> | |
| 12 <link rel="import" href="../logdog-stream/logdog-stream.html"> | |
| 13 | |
| 14 | |
| 15 <!-- | |
| 16 This element is the paging component, allowing a user to navigate through | |
| 17 list offsets. | |
| 18 | |
| 19 It is constructed as a separate element because it is repeated both above and | |
| 20 below the actual list. | |
| 21 | |
| 22 This has two "offset" concepts: the bound requested offset is the offset that | |
| 23 is bound to the "logdog-list-view" RPC offset parameter. The display offset | |
| 24 is the offset that the latest list refresh used. | |
| 25 | |
| 26 These are distinguished because we want the user's intent to change offset | |
| 27 (e.g., tap) event to immediately change the offset, but we don't want the | |
| 28 rendered offsets to actually update until the next set of list elements has | |
| 29 been loaded and rendered. | |
| 30 --> | |
| 31 <dom-module id="logdog-list-view-paging"> | |
| 32 | |
| 33 <style is="custom-style"> | |
| 34 #nav { | |
| 35 padding: 4px 4px 4px 4px; | |
| 36 margin: 10px 0px 10px 0px; | |
| 37 } | |
| 38 | |
| 39 a { | |
| 40 border-style: solid; | |
| 41 border-color: lightgray; | |
| 42 border-width: 1px; | |
| 43 user-select: none; | |
| 44 margin: 8px; | |
| 45 padding: 4px 4px 4px 4px; | |
| 46 cursor: pointer; | |
| 47 } | |
| 48 </style> | |
| 49 | |
| 50 <template> | |
| 51 <div id="nav"> | |
| 52 <!-- "Back to the beginning" zero button --> | |
| 53 <template is="dom-if" if="[[_hasValue(_links.zero)]]"> | |
| 54 <a on-click="_loadOffset" | |
| 55 data-args$="{{ _links.zero }}"> | |
| 56 [[_links.zero]] | |
| 57 </a> | |
| 58 </template> | |
| 59 | |
| 60 <!-- Previous page button --> | |
| 61 <template is="dom-if" if="[[_hasValue(_links.prev)]]"> | |
| 62 <a on-click="_loadOffset" | |
| 63 data-args$="{{ _links.prev }}"> | |
| 64 < [[_links.prev]] | |
| 65 </a> | |
| 66 </template> | |
| 67 | |
| 68 <!-- Next page button --> | |
| 69 <template is="dom-if" if="[[_hasValue(_links.next)]]"> | |
| 70 <a on-click="_loadOffset" | |
| 71 data-args$="{{ _links.next }}"> | |
| 72 [[_links.next]] > | |
| 73 </a> | |
| 74 </template> | |
| 75 </div> | |
| 76 </template> | |
| 77 | |
| 78 </dom-module> | |
| 79 | |
| 80 <script> | |
| 81 "use strict"; | |
| 82 | |
| 83 Polymer({ | |
| 84 is: 'logdog-list-view-paging', | |
| 85 properties: { | |
| 86 /** | |
| 87 * The request-bound offset parameter. This is changed when a button is | |
| 88 * clicked. | |
| 89 */ | |
| 90 offset: { | |
| 91 type: Number, | |
| 92 value: 0, | |
| 93 notify: true, | |
| 94 }, | |
| 95 | |
| 96 /** The offset of the currently-rendered list. Used to generate links. */ | |
| 97 displayOffset: { | |
| 98 type: Number, | |
| 99 value: 0, | |
| 100 notify: true, | |
| 101 }, | |
| 102 | |
| 103 /** True if there is at least one more page after the current one. */ | |
| 104 hasMore: { | |
| 105 type: Boolean, | |
| 106 notify: true, | |
| 107 }, | |
| 108 | |
| 109 /** The offset increment amount */ | |
| 110 size: { | |
| 111 type: Number, | |
| 112 value: 0, | |
| 113 notify: true, | |
| 114 }, | |
| 115 | |
| 116 /** A dictionary with the current zero, prev, and next offset values. */ | |
| 117 _links: { | |
| 118 computed: '_computeLinks(displayOffset, size, hasMore)', | |
| 119 }, | |
| 120 }, | |
| 121 | |
| 122 /** | |
| 123 * Computed function which rebuilds the links when any parameters change. | |
| 124 */ | |
| 125 _computeLinks: function(offset, size, hasMore) { | |
| 126 return { | |
| 127 next: (hasMore) ? (offset + size) : null, | |
| 128 prev: (offset > size) ? (offset - size) : null, | |
| 129 zero: (offset > 0) ? (0) : null, | |
| 130 }; | |
| 131 }, | |
| 132 | |
| 133 /** Filter function to test if a given value is not null. */ | |
| 134 _hasValue: function(v) { | |
| 135 return (v !== null); | |
| 136 }, | |
| 137 | |
| 138 /** Loads the offset from a nav button into the "offset" parameter. */ | |
| 139 _loadOffset: function(e) { | |
| 140 this.offset = parseInt(e.currentTarget.dataset.args || "0"); | |
| 141 }, | |
| 142 }); | |
| 143 </script> | |
| 144 | |
| 145 <!-- | |
| 146 An element for fetching complete LogDog log streams. | |
| 147 --> | |
| 148 <dom-module id="logdog-list-view"> | |
| 149 | |
| 150 <style> | |
| 151 #components { | |
| 152 background-color: var(--nav-background-color); | |
| 153 padding: 4px 4px 4px 4px; | |
| 154 margin: 4px 4px 4px 4px; | |
| 155 } | |
| 156 #components a { | |
| 157 color: var(--primary-text-color); | |
| 158 text-decoration: none; | |
| 159 } | |
| 160 #components ul { | |
| 161 padding: 0; | |
| 162 margin: 0; | |
| 163 } | |
| 164 #components li { | |
| 165 display: inline; | |
| 166 } | |
| 167 #components paper-material { | |
| 168 padding: 4px 4px 4px 4px; | |
| 169 margin-left: 4px; | |
| 170 margin-right: 4px; | |
| 171 display: inline-block; | |
| 172 background-color: var(--nav-item-color); | |
| 173 } | |
| 174 | |
| 175 #list { | |
| 176 text-decoration: none; | |
| 177 width: 90%; | |
| 178 margin: 10px 10px 10px 10px; | |
| 179 } | |
| 180 #list a { | |
| 181 color: var(--primary-text-color); | |
| 182 text-decoration: none; | |
| 183 } | |
| 184 #list ul { | |
| 185 padding: 0; | |
| 186 margin: 0; | |
| 187 list-style-type: none; | |
| 188 border-width: 1px; | |
| 189 border-color: darkgray; | |
| 190 border-style: solid; | |
| 191 } | |
| 192 #list li { | |
| 193 padding: 2px 2px 2px 2px; | |
| 194 margin: 5px 10px 5px 10px; | |
| 195 font-size: 1.1em; | |
| 196 } | |
| 197 #list li a { | |
| 198 display: block; | |
| 199 } | |
| 200 #list li:nth-of-type(odd) { | |
| 201 background-color: white; | |
| 202 } | |
| 203 #list li:nth-of-type(even) { | |
| 204 background-color: #f2f2f2; | |
| 205 } | |
| 206 #list .stream-component { | |
| 207 font-weight: bold; | |
| 208 } | |
| 209 </style> | |
| 210 | |
| 211 <template> | |
| 212 <!-- Load server description --> | |
| 213 <rpc-client | |
| 214 id="client" | |
| 215 auto-token | |
| 216 host="[[host]]" | |
| 217 service="logdog.Logs" | |
| 218 method="List" | |
| 219 request="[[_body]]" | |
| 220 last-response="{{lastResponse}}"></rpc-client> | |
| 221 | |
| 222 <!-- | |
| 223 The current set of components. For example, for "foo/bar/+/baz", this | |
| 224 expands into: | |
| 225 | |
| 226 [foo] [bar] [+] [baz] | |
| 227 --> | |
| 228 <div id="components"> | |
| 229 <ul> | |
| 230 <template is="dom-repeat" items="[[components]]"> | |
| 231 <li> | |
| 232 <paper-material elevation="1"> | |
| 233 <a href="[[linkBase]][[item.path]]">[[item.name]]</a> | |
| 234 </paper-material> | |
| 235 </li> | |
| 236 </template> | |
| 237 </ul> | |
| 238 </div> | |
| 239 | |
| 240 <!-- Prev/Next links (top) --> | |
| 241 <logdog-list-view-paging | |
| 242 base="[[base]]" | |
| 243 offset="{{offset}}" | |
| 244 display-offset="[[currentOffset]]" | |
| 245 has-more="[[_hasMore]]" | |
| 246 size="[[count]]"> | |
| 247 </logdog-list-view-paging> | |
| 248 | |
| 249 <!-- The current list view. --> | |
| 250 <div id="list" flex> | |
| 251 <ul> | |
| 252 <!-- Sub-paths (directories) --> | |
| 253 <template is="dom-repeat" items="[[paths]]"> | |
| 254 <li class="path-component"> | |
| 255 <a href="[[linkBase]][[item.full]]"> | |
| 256 [[item.value]] | |
| 257 </a> | |
| 258 </li> | |
| 259 </template> | |
| 260 | |
| 261 <!-- Stream contents (files) --> | |
| 262 <template is="dom-repeat" items="[[streams]]"> | |
| 263 <li class="stream-component"> | |
| 264 <a href="[[streamLinkBase]]?s=[[item.full]]"> | |
| 265 [[item.value]] | |
| 266 </a> | |
| 267 </li> | |
| 268 </template> | |
| 269 </ul> | |
| 270 </div> | |
| 271 | |
| 272 <!-- Prev/Next links (bottom) --> | |
| 273 <logdog-list-view-paging | |
| 274 base="[[base]]" | |
| 275 offset="{{offset}}" | |
| 276 display-offset="[[currentOffset]]" | |
| 277 has-more="[[_hasMore]]" | |
| 278 size="[[count]]"> | |
| 279 </logdog-list-view-paging> | |
| 280 </template> | |
| 281 | |
| 282 </dom-module> | |
| 283 | |
| 284 <script> | |
| 285 Polymer({ | |
| 286 is: "logdog-list-view", | |
| 287 properties: { | |
| 288 | |
| 289 hostAttributes: { | |
| 290 hidden: true, | |
| 291 }, | |
| 292 | |
| 293 /** The name ([host][:port]) of the pRPC host. */ | |
| 294 host: { | |
| 295 type: String, | |
| 296 notify: true, | |
| 297 }, | |
| 298 | |
| 299 /** Generated path links will have this prepended to them. */ | |
| 300 linkBase: { | |
| 301 type: String, | |
| 302 value: "", | |
| 303 notify: true, | |
| 304 }, | |
| 305 | |
| 306 /** | |
| 307 * Generated stream links will use this parameter, referencing the | |
| 308 * selected streams with "s" query parameters. | |
| 309 */ | |
| 310 streamLinkBase: { | |
| 311 type: String, | |
| 312 notify: true, | |
| 313 }, | |
| 314 | |
| 315 /** | |
| 316 * The current log list base. | |
| 317 * | |
| 318 * Base is a unified "project/path..." string. If empty, the list will be | |
| 319 * project-level. | |
| 320 */ | |
| 321 base: { | |
| 322 type: Object, | |
| 323 value: "", | |
| 324 notify: true, | |
| 325 }, | |
| 326 | |
| 327 /** The maximum number of list elements to request. */ | |
| 328 count: { | |
| 329 type: Number, | |
| 330 value: 50, | |
| 331 notify: true, | |
| 332 }, | |
| 333 | |
| 334 /** The list request offset. */ | |
| 335 offset: { | |
| 336 type: Number, | |
| 337 value: 0, | |
| 338 notify: true, | |
| 339 }, | |
| 340 | |
| 341 /** The path components under "base". */ | |
| 342 paths: { | |
| 343 type: Array, | |
| 344 value: function() { | |
| 345 return []; | |
| 346 }, | |
| 347 readOnly: true, | |
| 348 }, | |
| 349 | |
| 350 /** The stream components under "base". */ | |
| 351 streams: { | |
| 352 type: Array, | |
| 353 value: [], | |
| 354 readOnly: true, | |
| 355 }, | |
| 356 | |
| 357 /** | |
| 358 * Split "base" into components (e.g., "foo/bar" => [foo, foo/bar]) | |
| 359 * for navigation. | |
| 360 */ | |
| 361 components: { | |
| 362 type: Array, | |
| 363 value: [], | |
| 364 readOnly: true, | |
| 365 }, | |
| 366 | |
| 367 /** | |
| 368 * The "next" cursor value from the latest response. | |
| 369 * | |
| 370 * This will be null if there is no next cursor value. | |
| 371 */ | |
| 372 nextPage: { | |
| 373 type: String, | |
| 374 value: null, | |
| 375 readOnly: true, | |
| 376 }, | |
| 377 | |
| 378 /** The offset of the current list. */ | |
| 379 currentOffset: { | |
| 380 type: Number, | |
| 381 value: 0, | |
| 382 readOnly: true, | |
| 383 }, | |
| 384 | |
| 385 _body: { | |
| 386 computed: "_computeBody(base, count, offset)", | |
| 387 }, | |
| 388 | |
| 389 /** True if the latest request specified additional pages. */ | |
| 390 _hasMore: { | |
| 391 computed: "_computeHasMore(nextPage)", | |
| 392 }, | |
| 393 | |
| 394 lastResponse: { | |
| 395 type: Object, | |
| 396 observer: '_onLastResponseChanged', | |
| 397 }, | |
| 398 }, | |
| 399 | |
| 400 observers: [ | |
| 401 "_hostChanged(host, base)", | |
| 402 "_baseChanged(base)", | |
| 403 ], | |
| 404 | |
| 405 /** Called when the host value has changed. Reset. */ | |
| 406 _hostChanged: function(host, base) { | |
| 407 if (host !== this.host) { | |
| 408 this.base = "/"; | |
| 409 this._resetOffset(); | |
| 410 } | |
| 411 | |
| 412 // Enable automatic request sending once our "host" has loaded. */ | |
| 413 this.$.client.auto = (!!this.host); | |
| 414 }, | |
| 415 | |
| 416 /** Called when the base value has changed. */ | |
| 417 _baseChanged: function(base) { | |
| 418 this._resetOffset(); | |
| 419 }, | |
| 420 | |
| 421 /** Reset offset parameters. */ | |
| 422 _resetOffset: function() { | |
| 423 this.offset = 0; | |
| 424 this._setCurrentOffset(0); | |
| 425 }, | |
| 426 | |
| 427 /** | |
| 428 * Returns true if there are additional list results. We know this if the | |
| 429 * latest response supplied a non-empty "next" cursor value. | |
| 430 */ | |
| 431 _computeHasMore: function(nextPage) { | |
| 432 return (!!nextPage); | |
| 433 }, | |
| 434 | |
| 435 _computeBody: function(base, count, offset) { | |
| 436 var req = { | |
| 437 "maxResults": count, | |
| 438 "offset": offset, | |
| 439 }; | |
| 440 | |
| 441 // Split our base into project and path. | |
| 442 var lds = LogDogStream.splitProject(base); | |
| 443 req.project = lds.project; | |
| 444 req.pathBase = lds.path; | |
| 445 return req; | |
| 446 }, | |
| 447 | |
| 448 _onLastResponseChanged: function(resp) { | |
| 449 this._setNextPage(resp.next); | |
| 450 this._setCurrentOffset(this.offset); | |
| 451 | |
| 452 // Calculate our unified path base (project/path...). | |
| 453 var components = []; | |
| 454 if (resp.project) { | |
| 455 components.push(resp.project); | |
| 456 | |
| 457 if (resp.pathBase) { | |
| 458 components.push.apply(components, resp.pathBase.split("/")); | |
| 459 } | |
| 460 } | |
| 461 | |
| 462 // Calculate partial components. | |
| 463 if (components.length && components[0] === "") { | |
| 464 // Remove the initial "/" element. It will be forcefully inserted at the | |
| 465 // end. | |
| 466 components.shift(); | |
| 467 } | |
| 468 | |
| 469 // Generate our individual components. | |
| 470 this.base = components.join("/"); | |
| 471 components = components.map(function(cur, idx, arr) { | |
| 472 return { | |
| 473 name: cur, | |
| 474 path: arr.slice(0, idx+1).join("/"), | |
| 475 }; | |
| 476 }); | |
| 477 components.unshift({ | |
| 478 name: "/", | |
| 479 path: "", | |
| 480 }); | |
| 481 this._setComponents(components); | |
| 482 | |
| 483 // Calculate path components. | |
| 484 this._setPaths((resp.components || []).filter(function(e) { | |
| 485 return (e.type !== "STREAM"); | |
| 486 }).map(function(e) { | |
| 487 var full = e.name; | |
| 488 if (this.base !== "") { | |
| 489 full = this.base + "/" + full; | |
| 490 } | |
| 491 return { | |
| 492 value: e.name, | |
| 493 full: full, | |
| 494 }; | |
| 495 }.bind(this))); | |
| 496 | |
| 497 // Calculate stream components. | |
| 498 this._setStreams((resp.components || []).filter(function(e) { | |
| 499 return (e.type === "STREAM"); | |
| 500 }).map(function(e) { | |
| 501 var full = e.name; | |
| 502 if (resp.base !== "") { | |
| 503 full = this.base + "/" + full; | |
| 504 }; | |
| 505 return { | |
| 506 value: e.name, | |
| 507 full: full, | |
| 508 }; | |
| 509 }.bind(this))); | |
| 510 }, | |
| 511 }); | |
| 512 </script> | |
| OLD | NEW |