~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/registry/javascript/distroseries/initseries.js

  • Committer: Abel Deuring
  • Date: 2011-07-27 16:22:36 UTC
  • mfrom: (13539 devel)
  • mto: This revision was merged to the branch mainline in revision 13587.
  • Revision ID: abel.deuring@canonical.com-20110727162236-m5e4e45257ehq65r
devel merged

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 * Copyright 2011 Canonical Ltd. This software is licensed under the
3
3
 * GNU Affero General Public License version 3 (see the file LICENSE).
4
4
 *
5
 
 * DistroSeries related stuff.
 
5
 * DistroSeries Initialization.
6
6
 *
7
 
 * @module registry
8
 
 * @submodule distroseries
 
7
 * @module lp.registry.distroseries
 
8
 * @submodule initseries
9
9
 */
10
10
 
11
11
YUI.add('lp.registry.distroseries.initseries', function(Y) {
14
14
 
15
15
var namespace = Y.namespace('lp.registry.distroseries.initseries');
16
16
 
17
 
 
18
 
/**
19
 
 * A form row matching that which LaunchpadForm presents, containing a
20
 
 * field (defined in a subclass), and an optional label and
21
 
 * description.
22
 
 *
23
 
 * @class FormRowWidget
24
 
 */
25
 
var FormRowWidget = function() {
26
 
    FormRowWidget.superclass.constructor.apply(this, arguments);
27
 
};
28
 
 
29
 
Y.mix(FormRowWidget, {
30
 
 
31
 
    NAME: 'formRowWidget',
32
 
 
33
 
    ATTRS: {
34
 
 
35
 
        /**
36
 
         * The field name.
37
 
         *
38
 
         * @property name
39
 
         */
40
 
        name: {
41
 
            setter: function(value, name) {
42
 
                this.fieldNode.all("input, select").set("name", value);
43
 
            }
44
 
        },
45
 
 
46
 
        /**
47
 
         * The top label for the field.
48
 
         *
49
 
         * @property label
50
 
         */
51
 
        label: {
52
 
            getter: function() {
53
 
                return this.labelNode.get("text");
54
 
            },
55
 
            setter: function(value, name) {
56
 
                this.labelNode.set("text", value);
57
 
            }
58
 
        },
59
 
 
60
 
        /**
61
 
         * A dictionary {link:link, text:text} to populate
62
 
         * the pop-up help for the field.
63
 
         *
64
 
         * @property help
65
 
         */
66
 
        help: {
67
 
            getter: function() {
68
 
                return {link:this.helpNode.one('a')
69
 
                            .get("href"),
70
 
                        text:this.helpNode
71
 
                            .one('.invisible-link')
72
 
                            .get("text")};
73
 
            },
74
 
            setter: function(value, name) {
75
 
                if ((value.link !== undefined) &&
76
 
                    (value.text !== undefined)) {
77
 
                    this.helpNode.one('a').set("href", value.link);
78
 
                    this.helpNode.one('.invisible-link')
79
 
                        .set("text", value.text);
80
 
                    this.helpNode.removeClass('unseen');
81
 
                }
82
 
                else {
83
 
                    this.helpNode.addClass('unseen');
84
 
                }
85
 
            }
86
 
        },
87
 
 
88
 
        /**
89
 
         * A description shown near the field.
90
 
         *
91
 
         * @label description
92
 
         */
93
 
        description: {
94
 
            getter: function() {
95
 
                return this.descriptionNode.get("text");
96
 
            },
97
 
            setter: function(value, name) {
98
 
                this.descriptionNode.set("text", value);
99
 
            }
100
 
        }
101
 
    }
102
 
 
103
 
});
104
 
 
105
 
Y.extend(FormRowWidget, Y.Widget, {
106
 
 
107
 
    BOUNDING_TEMPLATE: "<tr></tr>",
108
 
 
109
 
    CONTENT_TEMPLATE: '<td colspan="2"></td>',
110
 
 
111
 
    initializer: function(config) {
112
 
        this.labelNode = Y.Node.create("<label />");
113
 
        this.helpNode = Y.Node.create(('<span class="helper unseen">'+
114
 
            '&nbsp;<a href=""' +
115
 
            'target="help" class="sprite maybe">&nbsp;' +
116
 
            '<span class="invisible-link"></span></a></span>'));
117
 
        this.fieldNode = Y.Node.create("<div></div>");
118
 
        this.descriptionNode = Y.Node.create('<p class="formHelp" />');
119
 
        this.spinnerNode = Y.Node.create(
120
 
            '<img src="/@@/spinner" alt="Loading..." />');
121
 
    },
122
 
 
123
 
    renderUI: function() {
124
 
        this.get("contentBox")
125
 
            .append(this.labelNode)
126
 
            .append(this.helpNode)
127
 
            .append(this.fieldNode)
128
 
            .append(this.descriptionNode);
129
 
    },
130
 
 
131
 
    /**
132
 
     * Show the spinner.
133
 
     *
134
 
     * @method showSpinner
135
 
     */
136
 
    showSpinner: function() {
137
 
        this.fieldNode.empty().append(this.spinnerNode);
138
 
    },
139
 
 
140
 
    /**
141
 
     * Hide the spinner.
142
 
     *
143
 
     * @method hideSpinner
144
 
     */
145
 
    hideSpinner: function() {
146
 
        this.spinnerNode.remove();
147
 
    },
148
 
 
149
 
    /**
150
 
     * Display an error.
151
 
     *
152
 
     * @method showError
153
 
     */
154
 
    showError: function(error) {
155
 
        var message = Y.Node.create('<p />').set("text", error);
156
 
        this.fieldNode.empty().append(message);
157
 
        Y.lazr.anim.red_flash({node: message}).run();
158
 
    }
159
 
 
160
 
});
161
 
 
162
 
namespace.FormRowWidget = FormRowWidget;
163
 
 
164
 
/**
165
 
 * A table to display, order, delete the selected parent series. Each parent
166
 
 * can also be made an overlay, and a component and a pocket selected.
167
 
 *
168
 
 */
169
 
var ParentSeriesListWidget = function() {
170
 
    ParentSeriesListWidget
171
 
        .superclass.constructor.apply(this, arguments);
172
 
};
173
 
 
174
 
Y.mix(ParentSeriesListWidget, {
175
 
 
176
 
    NAME: 'parentSeriesListWidget',
177
 
 
178
 
    ATTRS: {
179
 
 
180
 
        /**
181
 
         * The DistroSeries the choices in this field should
182
 
         * reflect. Takes the form of a list of ids,
183
 
         * e.g. ["4", "55"].
184
 
         *
185
 
         * @property parents
186
 
         */
187
 
        parents: {
188
 
            getter: function() {
189
 
                var series = [];
190
 
                this.fieldNode.all("tbody > tr.parent").each(
191
 
                    function(node) {
192
 
                        series.push(
193
 
                            node.get('id').replace('parent-',''));
194
 
                    }
195
 
                );
196
 
                return series;
197
 
            }
198
 
        },
199
 
        overlays: {
200
 
            getter: function() {
201
 
                var overlays = [];
202
 
                this.fieldNode.all("tbody > tr.parent").each(
203
 
                    function(node) {
204
 
                        overlays.push(
205
 
                            node.one('input.overlay').get('checked'));
206
 
                    }
207
 
                );
208
 
                return overlays;
209
 
            }
210
 
        },
211
 
        overlay_pockets: {
212
 
            getter: function() {
213
 
                var overlay_pockets = [];
214
 
                this.fieldNode.all("tbody > tr.parent").each(
215
 
                    function(node) {
216
 
                        var select = node.one('td.pocket').one('select');
217
 
                        if (select !== null) {
218
 
                            overlay_pockets.push(select.get('value'));
219
 
                        }
220
 
                        else {
221
 
                            overlay_pockets.push(null);
222
 
                        }
223
 
                    }
224
 
                );
225
 
                return overlay_pockets;
226
 
            }
227
 
        },
228
 
        overlay_components: {
229
 
            getter: function() {
230
 
                var overlay_components = [];
231
 
                this.fieldNode.all("tbody > tr.parent").each(
232
 
                    function(node) {
233
 
                        var select = node.one('td.component').one('select');
234
 
                        if (select !== null) {
235
 
                            overlay_components.push(select.get('value'));
236
 
                        }
237
 
                        else {
238
 
                            overlay_components.push(null);
239
 
                        }
240
 
                    }
241
 
                );
242
 
                return overlay_components;
243
 
            }
244
 
        }
245
 
    }
246
 
});
247
 
 
248
 
Y.extend(ParentSeriesListWidget, FormRowWidget, {
249
 
 
250
 
    initializer: function() {
251
 
        ParentSeriesListWidget.superclass.initializer();
252
 
        this.client = new Y.lp.client.Launchpad();
253
 
        this.clean_display();
254
 
    },
255
 
 
256
 
    /**
257
 
     * Display a simple message when no parent series are selected.
258
 
     *
259
 
     * @method clean_display
260
 
     */
261
 
    clean_display: function() {
262
 
        this.fieldNode.empty();
263
 
        this.fieldNode.append(Y.Node.create("<div />")
264
 
            .set('text', '[No parent for this series yet!]'));
265
 
    },
266
 
 
267
 
    /**
268
 
     * Display the table header.
269
 
     *
270
 
     * @method init_display
271
 
     */
272
 
    init_display: function() {
273
 
        this.fieldNode.empty();
274
 
        this.fieldNode = Y.Node.create("<table />");
275
 
        var table_header = Y.Node.create(
276
 
            ["<thead><tr>",
277
 
             "<th>Order</th>",
278
 
             "<th>Parent name</th>",
279
 
             "<th>Overlay?</th>",
280
 
             "<th>Component</th>",
281
 
             "<th>Pocket</th>",
282
 
             "<th>Delete</th>",
283
 
             "</tr></thead>",
284
 
             "<tbody>",
285
 
             "</tbody>"
286
 
            ].join(""));
287
 
        this.fieldNode.append(table_header);
288
 
    },
289
 
 
290
 
    /**
291
 
     * Helper method to create a select widget from a list and add it
292
 
     * to a node.
293
 
     *
294
 
     * @method build_selector
295
 
     */
296
 
    build_selector: function(node, res_list, class_name) {
297
 
        var select = Y.Node.create('<select disabled="disabled"/>');
298
 
        res_list.forEach(
299
 
            function(choice) {
300
 
                select.appendChild('<option />')
301
 
                    .set('text', choice)
302
 
                    .set('value', choice);
303
 
            });
304
 
        node.one('td.'+class_name).append(select);
305
 
        node.one('input').on('click', function(e) {
306
 
            var select = node.one('td.'+class_name).one('select');
307
 
            if (select.hasAttribute('disabled')) {
308
 
                select.removeAttribute('disabled');
309
 
            }
310
 
            else {
311
 
                select.setAttribute('disabled', 'disabled');
312
 
            }
313
 
        });
314
 
    },
315
 
 
316
 
    /**
317
 
     * Build a select widget from a list retrieved from the api.
318
 
     *
319
 
     * @method build_select
320
 
     */
321
 
    build_select: function(node, class_name, path) {
322
 
        var self = this;
323
 
        var on = {
324
 
            success: function(res_list) {
325
 
                self.build_selector(node, res_list, class_name);
326
 
            },
327
 
            failure: function() {
328
 
                var failed_node = Y.Node.create('<span />')
329
 
                    .set('text', 'Failed to retrieve content.');
330
 
                node.one('td.'+class_name).append(failed_node);
331
 
                self.disable_overlay(node);
332
 
            }
333
 
        };
334
 
        this.client.get(path, {on: on});
335
 
     },
336
 
 
337
 
 
338
 
    /**
339
 
     * Move down a parent's line in the table.
340
 
     *
341
 
     * @method move_down
342
 
     */
343
 
    move_down: function(parent_id) {
344
 
        var node = this.fieldNode.one("tr#parent-" + parent_id);
345
 
        var other = node.next('tr.parent');
346
 
        if (other !== null) { node.swap(other);}
347
 
        Y.lazr.anim.green_flash({node: node}).run();
348
 
    },
349
 
 
350
 
    /**
351
 
     * Move up a parent's line in the table.
352
 
     *
353
 
     * @method move_up
354
 
     */
355
 
    move_up: function(parent_id) {
356
 
        var node = this.fieldNode.one("tr#parent-" + parent_id);
357
 
        var other = node.previous('tr.parent');
358
 
        if (other !== null) { node.swap(other);}
359
 
        Y.lazr.anim.green_flash({node: node}).run();
360
 
    },
361
 
 
362
 
    /**
363
 
     * Remove a parent series.
364
 
     *
365
 
     * @method remove_parent
366
 
     */
367
 
    remove_parent: function(parent_id) {
368
 
        if (this.get('parents').length === 1) {
369
 
            this.clean_display();
370
 
            this.renderUI();
371
 
        }
372
 
        else {
373
 
            this.fieldNode.one('tr#parent-' + parent_id).remove();
374
 
        }
375
 
        Y.fire("parent_removed", parent_id);
376
 
    },
377
 
 
378
 
    /**
379
 
     * Disable the overlay (used when something goes wrong fetching possible
380
 
     * components or pockets for the overlay).
381
 
     *
382
 
     * @method disable_overlay
383
 
     */
384
 
    disable_overlay: function(parent_node) {
385
 
        var overlay = parent_node.one('input.overlay');
386
 
        if (overlay.get('disabled') !== 'disabled') {
387
 
            Y.lazr.anim.red_flash({node: parent_node}).run();
388
 
            parent_node.one('input.overlay').set('disabled','disabled');
389
 
        }
390
 
    },
391
 
 
392
 
    /**
393
 
     * Add a parent series.
394
 
     *
395
 
     * @method add_parent
396
 
     */
397
 
    add_parent: function(parent) {
398
 
        if (this.get('parents').length === 0) {
399
 
            this.init_display();
400
 
            this.renderUI();
401
 
        }
402
 
        var item = this.fieldNode.one('tr#parent-' + parent.value);
403
 
        if (item !== null) {
404
 
            Y.lazr.anim.red_flash({node: item}).run();
405
 
            return false;
406
 
        }
407
 
        item =  Y.Node.create("<tr />")
408
 
            .addClass('parent')
409
 
            .set('id', 'parent-' + parent.value)
410
 
            .append(Y.Node.create("<td />")
411
 
                .append(Y.Node.create('<a href="" title="Move parent up"/>')
412
 
                    .addClass('move-up')
413
 
                .set('innerHTML', '&uarr;'))
414
 
                .append(Y.Node.create('<a href="" title="Move parent down"/>')
415
 
                    .addClass('move-down')
416
 
                .set('innerHTML', '&darr;')))
417
 
            .append(Y.Node.create("<td />")
418
 
                .addClass('series')
419
 
                .set('text', parent.title))
420
 
            .append(Y.Node.create("<td />")
421
 
                .set('align', 'center')
422
 
                .append(Y.Node.create('<input type="checkbox" />')
423
 
                    .addClass('overlay')))
424
 
            .append(Y.Node.create("<td />")
425
 
                .addClass('component'))
426
 
            .append(Y.Node.create("<td />")
427
 
                .addClass('pocket'))
428
 
            .append(Y.Node.create("<td />")
429
 
                .set('align', 'center')
430
 
                .append(Y.Node.create('<span />')
431
 
                    .addClass('sprite')
432
 
                    .addClass('remove')));
433
 
        this.fieldNode.one('tbody').append(item);
434
 
        this.build_select(item, 'component',
435
 
            parent.api_uri + '/component_names');
436
 
        this.build_select(item, 'pocket',
437
 
            parent.api_uri + '/suite_names');
438
 
        item.one('.move-up').on('click', function(e) {
439
 
            this.move_up(parent.value);
440
 
            e.preventDefault();
441
 
            return false;
442
 
        }, this);
443
 
        item.one('.move-down').on('click', function(e) {
444
 
            this.move_down(parent.value);
445
 
            e.preventDefault();
446
 
            return false;
447
 
        }, this);
448
 
        item.one('.remove').on('click', function(e) {
449
 
            var parent_id = item.get('id').replace('parent-','');
450
 
            this.remove_parent(parent_id);
451
 
            e.preventDefault();
452
 
            return false;
453
 
        }, this);
454
 
 
455
 
        Y.lazr.anim.green_flash({node: item}).run();
456
 
        return true;
457
 
    }
458
 
});
459
 
 
460
 
namespace.ParentSeriesListWidget = ParentSeriesListWidget;
461
 
 
462
 
 
463
 
/**
464
 
 * A form row matching that which LaunchpadForm presents, containing a
465
 
 * list of checkboxes, and an optional label and description.
466
 
 *
467
 
 * @class ChoiceListWidget
468
 
 */
469
 
var ChoiceListWidget = function() {
470
 
    ChoiceListWidget.superclass.constructor.apply(this, arguments);
471
 
};
472
 
 
473
 
Y.mix(ChoiceListWidget, {
474
 
 
475
 
    NAME: 'choiceListWidget',
476
 
 
477
 
    ATTRS: {
478
 
 
479
 
        /**
480
 
         * An array of strings from which to choose.
481
 
         *
482
 
         * @property choices
483
 
         */
484
 
        choices: {
485
 
            getter: function() {
486
 
                return this.fieldNode.all("li > input").get("value");
487
 
            },
488
 
            setter: function(value, name) {
489
 
                var choices = Y.Array.unique(value).sort();
490
 
                var list = Y.Node.create("<ul />");
491
 
                var self = this;
492
 
                choices.forEach(
493
 
                    function(choice) {
494
 
                       var item = self._createChoice(choice);
495
 
                       list.append(item);
496
 
                    }
497
 
                );
498
 
                this.fieldNode.empty().append(list);
499
 
            }
500
 
        },
501
 
 
502
 
        /**
503
 
         * The current selection.
504
 
         *
505
 
         * @property choice
506
 
         */
507
 
        choice: {
508
 
            setter: function(value, name) {
509
 
                if (!Y.Lang.isArray(value)) {
510
 
                    value = [value];
511
 
                }
512
 
                this.fieldNode.all("li > input").each(
513
 
                    function(node) {
514
 
                        node.set(
515
 
                            "checked",
516
 
                            value.indexOf(node.get("value")) >= 0);
517
 
                    }
518
 
                );
519
 
            },
520
 
            getter: function() {
521
 
                var choice = [];
522
 
                this.fieldNode.all("li > input").each(
523
 
                    function(node) {
524
 
                        if (node.get("checked")) {
525
 
                            choice.push(node.get("value"));
526
 
                        }
527
 
                    }
528
 
                );
529
 
                if (this.get("type") === "radio") {
530
 
                    if (choice.length === 0) {
531
 
                        choice = null;
532
 
                    }
533
 
                    else if (choice.length === 1) {
534
 
                        choice = choice[0];
535
 
                    }
536
 
                    else {
537
 
                        choice = undefined;
538
 
                    }
539
 
                }
540
 
                return choice;
541
 
            }
542
 
        },
543
 
 
544
 
        /**
545
 
         * The input type to display. Choose from "checkbox" or "radio".
546
 
         *
547
 
         * @property type
548
 
         */
549
 
        type: {
550
 
            value: "checkbox",
551
 
            setter: function(value, name) {
552
 
                this.fieldNode.all("li > input").set("type", value);
553
 
            }
554
 
        }
555
 
 
556
 
    }
557
 
 
558
 
});
559
 
 
560
 
Y.extend(ChoiceListWidget, FormRowWidget, {
561
 
 
562
 
    /**
563
 
     * Helper method to create an entry for the select widget.
564
 
     *
565
 
     * @method _createChoice
566
 
     */
567
 
    _createChoice: function(choice) {
568
 
         var field_name = this.get("name");
569
 
         var field_type = this.get("type");
570
 
         var item = Y.Node.create(
571
 
            "<li><input /> <label /></li>");
572
 
        item.one("input")
573
 
            .set("type", field_type)
574
 
            .set("name", field_name)
575
 
            .set("value", choice);
576
 
        item.one("label")
577
 
            .setAttribute(
578
 
                "for", item.one("input").generateID())
579
 
            .setStyle("font-weight", "normal")
580
 
            .set("text", choice);
581
 
        return item;
582
 
    },
583
 
 
584
 
   /**
585
 
     * Remove a list of choices from the possible widget's choices.
586
 
     *
587
 
     * @method remove_choices
588
 
     */
589
 
    remove_choices: function(choices) {
590
 
        choices.forEach(
591
 
            function(choice) {
592
 
                this.fieldNode.all("select > option").each(
593
 
                    function(option) { options.push(option); });
594
 
                this.fieldNode.all(
595
 
                    "li input[value=" + choice + "]").each(
596
 
                        function(li_input) {
597
 
                            li_input.get('parentNode').remove();
598
 
                        }
599
 
                );
600
 
            },
601
 
            this
602
 
        );
603
 
        Y.lazr.anim.green_flash({node: this.fieldNode}).run();
604
 
    },
605
 
 
606
 
    _sorted_position: function(choice) {
607
 
        var options = [];
608
 
        this.fieldNode.all("input").each(
609
 
            function(node) {
610
 
                options.push(node.get('value'));
611
 
            }
612
 
        );
613
 
        options.push(choice);
614
 
        return options.sort().indexOf(choice);
615
 
    },
616
 
 
617
 
   /**
618
 
     * Add new choices (if they are not already present).
619
 
     *
620
 
     * @method add_choices
621
 
     */
622
 
    add_choices: function(new_choices) {
623
 
        new_choices.forEach(
624
 
            function(choice) {
625
 
                if (this.fieldNode.all(
626
 
                    "li > input[value=" + choice + "]").isEmpty()) {
627
 
                    var list = this.fieldNode.one('ul');
628
 
                    if (list === null) {
629
 
                        list = Y.Node.create("<ul />");
630
 
                        this.fieldNode.empty().append(list);
631
 
                    }
632
 
                    var option = this._createChoice(choice);
633
 
                    var options = list.all('input');
634
 
                    if (options.isEmpty()) {
635
 
                        list.append(option);
636
 
                    }
637
 
                    else {
638
 
                        var pos = this._sorted_position(choice);
639
 
                        if (pos === 0) {
640
 
                            list.prepend(option);
641
 
                        }
642
 
                        else {
643
 
                            list.insertBefore(option, options.item(pos));
644
 
                        }
645
 
                    }
646
 
                }
647
 
            }, this
648
 
        );
649
 
        Y.lazr.anim.green_flash({node: this.fieldNode}).run();
650
 
    }
651
 
 
652
 
});
653
 
 
654
 
 
655
 
namespace.ChoiceListWidget = ChoiceListWidget;
656
 
 
657
 
 
658
 
/**
659
 
 * A special form of ChoiceListWidget for choosing architecture tags.
660
 
 *
661
 
 * @class ArchitecturesChoiceListWidget
662
 
 */
663
 
var ArchitecturesChoiceListWidget = function() {
664
 
    ArchitecturesChoiceListWidget
665
 
        .superclass.constructor.apply(this, arguments);
666
 
};
667
 
 
668
 
Y.mix(ArchitecturesChoiceListWidget, {
669
 
 
670
 
    NAME: 'architecturesChoiceListWidget',
671
 
 
672
 
    ATTRS: {
673
 
    }
674
 
 
675
 
});
676
 
 
677
 
Y.extend(ArchitecturesChoiceListWidget, ChoiceListWidget, {
678
 
 
679
 
    initializer: function(config) {
680
 
        this.client = new Y.lp.client.Launchpad();
681
 
        this.error_handler = new Y.lp.client.ErrorHandler();
682
 
        this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
683
 
        this.error_handler.showError = Y.bind(this.showError, this);
684
 
        this._distroseries = {};
685
 
        this.clean_display();
686
 
    },
687
 
 
688
 
    /**
689
 
     * Display a simple message when no parent series are selected.
690
 
     *
691
 
     * @method clean_display
692
 
     */
693
 
    clean_display: function() {
694
 
        this.fieldNode.empty();
695
 
        this.fieldNode.append(Y.Node.create("<div />")
696
 
            .set('text', '[No architectures to select from yet!]'));
697
 
        this.renderUI();
698
 
    },
699
 
 
700
 
    /**
701
 
     * Add a parent distroseries, add the architectures for this new
702
 
     * distroseries to the possible choices.
703
 
     *
704
 
     * @method add_distroseries
705
 
     * @param {Object} The distroseries to add ({value:distroseries_id),
706
 
     *     api_uri:distroseries_uri}).
707
 
     */
708
 
    add_distroseries: function(distroseries) {
709
 
        var path = distroseries.api_uri + "/architectures";
710
 
        var distroseries_id = distroseries.value;
711
 
        var self = this;
712
 
        var on = {
713
 
            success: function (results) {
714
 
                self.add_distroarchseries(distroseries_id, results);
715
 
            },
716
 
            failure: this.error_handler.getFailureHandler()
717
 
        };
718
 
        this.client.get(path, {on: on});
719
 
     },
720
 
 
721
 
    /**
722
 
     * Remove a parent distroseries, remove the architectures only
723
 
     * present in this parent series from the possible choices.
724
 
     *
725
 
     * @method remove_distroseries
726
 
     */
727
 
    remove_distroseries: function(distroseries_id) {
728
 
        // Compute which das is only in the distroseries to be removed.
729
 
        arch_to_remove = [];
730
 
        var das = this._distroseries[distroseries_id];
731
 
        var i, ds, j;
732
 
        for (i=0; i<das.entries.length; i++) {
733
 
            remove_das = true;
734
 
            arch = das.entries[i].get('architecture_tag');
735
 
            for (ds in this._distroseries) {
736
 
                if (ds !== distroseries_id) {
737
 
                   var other_das = this._distroseries[ds];
738
 
                   for (j=0; j<other_das.entries.length; j++) {
739
 
                       var other_arch = other_das.entries[j].get(
740
 
                           'architecture_tag');
741
 
                       if (other_arch === arch) {
742
 
                           remove_das = false;
743
 
                       }
744
 
                   }
745
 
                }
746
 
            }
747
 
            if (remove_das) {
748
 
                arch_to_remove.push(arch);
749
 
            }
750
 
        }
751
 
        delete this._distroseries[distroseries_id];
752
 
        this.remove_choices(arch_to_remove);
753
 
        if (this.fieldNode.all('input').isEmpty()) {
754
 
            this.clean_display();
755
 
        }
756
 
    },
757
 
 
758
 
    /**
759
 
     * Add a list of distroarchseries.
760
 
     *
761
 
     * @method add_distroarchseries
762
 
     * @param {String} distroseries_id The distroarchseries id.
763
 
     * @param {Object} distroarchseries The distroarchseries object.
764
 
     */
765
 
    add_distroarchseries: function(distroseries_id, distroarchseries) {
766
 
        this._distroseries[distroseries_id] = distroarchseries;
767
 
        var choices = distroarchseries.entries.map(
768
 
            function(das) {
769
 
                return das.get("architecture_tag");
770
 
            }
771
 
        );
772
 
        this.add_choices(choices);
773
 
     }
774
 
 
775
 
});
776
 
 
777
 
namespace.ArchitecturesChoiceListWidget = ArchitecturesChoiceListWidget;
778
 
 
779
 
 
780
 
/**
781
 
 * A special form of FormRowWidget, containing a select control.
782
 
 *
783
 
 * @class SelectWidget
784
 
 */
785
 
var SelectWidget = function() {
786
 
    SelectWidget.superclass.constructor.apply(this, arguments);
787
 
};
788
 
 
789
 
Y.mix(SelectWidget, {
790
 
 
791
 
    NAME: 'selectWidget',
792
 
 
793
 
    ATTRS: {
794
 
 
795
 
        /**
796
 
         * An array of objects from which to choose. Each object
797
 
         * should contain a value for "value", "text" and "data".
798
 
         *
799
 
         * @property choices
800
 
         */
801
 
        choices: {
802
 
            getter: function() {
803
 
                /* I think this is a YUI3 wart; I can't see any way to
804
 
                   map() over a NodeList, so I must push the elements
805
 
                   one by one into an array first. */
806
 
                var options = Y.Array([]);
807
 
                this.fieldNode.all("select > option").each(
808
 
                    function(option) { options.push(option); });
809
 
                return options.map(
810
 
                    function(option) {
811
 
                        return {
812
 
                            value: option.get("value"),
813
 
                            text: option.get("text"),
814
 
                            data: option.getData("data")
815
 
                        };
816
 
                    }
817
 
                );
818
 
            },
819
 
            setter: function(value, name) {
820
 
                var select = Y.Node.create("<select />");
821
 
                select.set("name", this.get("name"))
822
 
                      .set("size", this.get("size"));
823
 
                if (this.get("multiple")) {
824
 
                    select.set("multiple", "multiple");
825
 
                }
826
 
                var choices = Y.Array(value);
827
 
                choices.forEach(
828
 
                    function(choice) {
829
 
                        var option = Y.Node.create("<option />");
830
 
                        option.set("value", choice.value)
831
 
                              .set("text", choice.text)
832
 
                              .setData("data", choice.data);
833
 
                        select.append(option);
834
 
                    }
835
 
                );
836
 
                if (choices.length > 0) {
837
 
                    this.fieldNode.empty().append(select);
838
 
                }
839
 
                else {
840
 
                    this.fieldNode.empty();
841
 
                }
842
 
            }
843
 
        },
844
 
 
845
 
        /**
846
 
         * The current selection.
847
 
         *
848
 
         * @property choice
849
 
         */
850
 
        choice: {
851
 
            setter: function(value, name) {
852
 
                if (!Y.Lang.isArray(value)) {
853
 
                    value = [value];
854
 
                }
855
 
                this.fieldNode.all("select > option").each(
856
 
                    function(node) {
857
 
                        node.set(
858
 
                            "selected",
859
 
                            value.indexOf(node.get("value")) >= 0);
860
 
                    }
861
 
                );
862
 
            },
863
 
            getter: function() {
864
 
                var choice = [];
865
 
                this.fieldNode.all("select > option").each(
866
 
                    function(node) {
867
 
                        if (node.get("selected")) {
868
 
                            choice.push(node.get("value"));
869
 
                        }
870
 
                    }
871
 
                );
872
 
                return choice;
873
 
            }
874
 
        },
875
 
 
876
 
        /**
877
 
         * The number of rows to show in the select widget.
878
 
         *
879
 
         * @property size
880
 
         */
881
 
        size: {
882
 
            value: 1,
883
 
            setter: function(value, name) {
884
 
                this.fieldNode.all("select").set("size", value);
885
 
            }
886
 
        },
887
 
 
888
 
        /**
889
 
         * Whether multiple rows can be selected.
890
 
         *
891
 
         * @property multiple
892
 
         */
893
 
        multiple: {
894
 
            value: false,
895
 
            setter: function(value, name) {
896
 
                value = value ? true : false;
897
 
                this.fieldNode.all("select").set("multiple", value);
898
 
                return value;
899
 
            }
900
 
        }
901
 
 
902
 
    }
903
 
 
904
 
});
905
 
 
906
 
Y.extend(SelectWidget, FormRowWidget, {
907
 
 
908
 
    _sorted_position: function(choice) {
909
 
        var options = [];
910
 
        this.fieldNode.all("option").each(
911
 
            function(node) {
912
 
                options.push(node.get('text'));
913
 
            }
914
 
        );
915
 
        options.push(choice);
916
 
        return options.sort().indexOf(choice);
917
 
    },
918
 
 
919
 
    /**
920
 
     * Choose a size for the select control based on the number of
921
 
     * choices, up to an optional maximum size.
922
 
     *
923
 
     * @method autoSize
924
 
     */
925
 
    autoSize: function(maxSize) {
926
 
        var choiceCount = this.fieldNode.all("select > option").size();
927
 
        if (choiceCount === 0) {
928
 
            this.set("size", 1);
929
 
        }
930
 
        else if (maxSize === undefined) {
931
 
            this.set("size", choiceCount);
932
 
        }
933
 
        else if (choiceCount < maxSize) {
934
 
            this.set("size", choiceCount);
935
 
        }
936
 
        else {
937
 
            this.set("size", maxSize);
938
 
        }
939
 
        return this;
940
 
    }
941
 
 
942
 
});
943
 
 
944
 
namespace.SelectWidget = SelectWidget;
945
 
 
946
 
 
947
 
/**
948
 
 * A special form of SelectWidget for choosing packagesets.
949
 
 *
950
 
 * @class PackagesetPickerWidget
951
 
 */
952
 
var PackagesetPickerWidget = function() {
953
 
    PackagesetPickerWidget
954
 
        .superclass.constructor.apply(this, arguments);
955
 
};
956
 
 
957
 
Y.mix(PackagesetPickerWidget, {
958
 
 
959
 
    NAME: 'packagesetPickerWidget',
960
 
 
961
 
    ATTRS: {
962
 
 
963
 
        /**
964
 
         * The DistroSeries the choices in this field should
965
 
         * reflect. Takes the form of a string, e.g. "ubuntu/hoary".
966
 
         *
967
 
         * @property distroSeries
968
 
         */
969
 
        distroSeries: {
970
 
            setter: function(value, name) {
971
 
                var distro_series_uri = Y.lp.client.get_absolute_uri(value);
972
 
                var on = {
973
 
                    start: Y.bind(this.showSpinner, this),
974
 
                    success: Y.bind(this.set, this, "packageSets"),
975
 
                    failure: this.error_handler.getFailureHandler(),
976
 
                    end: Y.bind(this.hideSpinner, this)
977
 
                };
978
 
                var config = {
979
 
                    on: on,
980
 
                    parameters: {
981
 
                        distroseries: distro_series_uri
982
 
                    }
983
 
                };
984
 
                this.client.named_get("package-sets", "getBySeries", config);
985
 
            }
986
 
        }
987
 
    }
988
 
});
989
 
 
990
 
 
991
 
Y.extend(PackagesetPickerWidget, SelectWidget, {
992
 
 
993
 
    /**
994
 
     * Add a distroseries: add its packagesets to the packageset picker.
995
 
     *
996
 
     * @method add_distroseries
997
 
     * @param {Object} distroseries The distroseries object.
998
 
     */
999
 
    add_distroseries: function(distroseries) {
1000
 
        var distro_series_uri = Y.lp.client.get_absolute_uri(
1001
 
            distroseries.api_uri);
1002
 
        var self = this;
1003
 
        var on = {
1004
 
            success: function (results) {
1005
 
                self.add_packagesets(results, distroseries);
1006
 
            },
1007
 
            failure: this.error_handler.getFailureHandler()
1008
 
        };
1009
 
        var config = {
1010
 
            on: on,
1011
 
            parameters: {
1012
 
                distroseries: distro_series_uri
1013
 
            }
1014
 
        };
1015
 
        this.client.named_get("package-sets", "getBySeries", config);
1016
 
    },
1017
 
 
1018
 
    /**
1019
 
     * Display a simple message when no parent series are selected.
1020
 
     *
1021
 
     * @method clean_display
1022
 
     */
1023
 
    clean_display: function() {
1024
 
        this.fieldNode.empty();
1025
 
        this.fieldNode.append(Y.Node.create("<div />")
1026
 
            .set('text', '[No package sets to select from yet!]'));
1027
 
        this.renderUI();
1028
 
    },
1029
 
 
1030
 
    /**
1031
 
     * Initialize the picker's select node.
1032
 
     *
1033
 
     * @method init_select
1034
 
     */
1035
 
    init_select: function() {
1036
 
        var select = this.fieldNode.one('select');
1037
 
        if (select === null) {
1038
 
            select = Y.Node.create("<select />");
1039
 
            select.set("name", this.get("name"))
1040
 
                    .set("size", this.get("size"));
1041
 
            if (this.get("multiple")) {
1042
 
                select.set("multiple", "multiple");
1043
 
            }
1044
 
            this.fieldNode.empty().append(select);
1045
 
        }
1046
 
        return select;
1047
 
    },
1048
 
 
1049
 
    /**
1050
 
     * Add a choice to the picker.
1051
 
     *
1052
 
     * @method add_choice
1053
 
     * @param {Object} choice The choice object to be added
1054
 
     *     ({text: choice_text, value: choice_value, data: choice_data}).
1055
 
     */
1056
 
    add_choice: function(choice) {
1057
 
        var select = this.init_select();
1058
 
        var option = Y.Node.create("<option />");
1059
 
        option.set("value", choice.value)
1060
 
            .set("text", choice.text)
1061
 
            .setData("data", choice.data);
1062
 
        var options = select.all('option');
1063
 
        if (options.isEmpty()) {
1064
 
            select.append(option);
1065
 
        }
1066
 
        else {
1067
 
            var pos = this._sorted_position(choice.text);
1068
 
            if (pos === 0) {
1069
 
                select.prepend(option);
1070
 
            }
1071
 
            else {
1072
 
                select.insertBefore(option, options.item(pos));
1073
 
            }
1074
 
        }
1075
 
    },
1076
 
 
1077
 
    /**
1078
 
     * Add choices (a set of packagesets) to the picker.
1079
 
     *
1080
 
     * @method add_packagesets
1081
 
     * @param {Y.lp.client.Collection} packagesets The collection of
1082
 
     *     packagesets to add.
1083
 
     * @param {Object} distroseries The distroseries object
1084
 
     *     ({value:distroseries_id), api_uri:distroseries_uri}).
1085
 
     */
1086
 
    add_packagesets: function(packagesets, distroseries) {
1087
 
        this._packagesets[distroseries.value] = packagesets.entries;
1088
 
        packagesets.entries.forEach(
1089
 
            function(packageset) {
1090
 
                var value = packageset.get("id");
1091
 
                this.add_choice({
1092
 
                    data: packageset,
1093
 
                    value: value,
1094
 
                    text: (
1095
 
                        packageset.get("name") + ": " +
1096
 
                        packageset.get("description") +
1097
 
                        " (" + distroseries.title + ") ")
1098
 
                });
1099
 
            }, this);
1100
 
        this.autoSize(10);
1101
 
        Y.lazr.anim.green_flash({node: this.fieldNode}).run();
1102
 
     },
1103
 
 
1104
 
    /**
1105
 
     * Remove a distroseries: remove its packagesets from the picker.
1106
 
     *
1107
 
     * @method remove_distroseries
1108
 
     * @param {String} distroseries_id The id of the distroseries to be
1109
 
     *     removed.
1110
 
     */
1111
 
    remove_distroseries: function(distroseries_id) {
1112
 
        this._packagesets[distroseries_id].forEach(
1113
 
            function(packageset) {
1114
 
                this.fieldNode.one(
1115
 
                    'option[value="' + packageset.get("id") + '"]').remove();
1116
 
            }, this);
1117
 
       Y.lazr.anim.green_flash({node: this.fieldNode}).run();
1118
 
        if (this.fieldNode.all('option').isEmpty()) {
1119
 
            this.clean_display();
1120
 
        }
1121
 
    },
1122
 
 
1123
 
    initializer: function(config) {
1124
 
        this.client = new Y.lp.client.Launchpad();
1125
 
        this.error_handler = new Y.lp.client.ErrorHandler();
1126
 
        this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
1127
 
        this.error_handler.showError = Y.bind(this.showError, this);
1128
 
        this.clean_display();
1129
 
        // _packagesets maps each distroseries' id to a collection of ids
1130
 
        // of its packagesets.
1131
 
        // It's populated each time a new distroseries is added as a parent
1132
 
        // and used when a distroseries is removed to get all the
1133
 
        // corresponding packagesets to be removed from the widget.
1134
 
        this._packagesets = {};
1135
 
    }
1136
 
 
1137
 
});
1138
 
 
1139
 
namespace.PackagesetPickerWidget = PackagesetPickerWidget;
1140
 
 
1141
 
 
1142
 
/**
1143
 
 * A widget to encapsulate functionality around the form actions.
1144
 
 *
1145
 
 * @class FormActionsWidget
1146
 
 */
1147
 
var FormActionsWidget = function() {
1148
 
    FormActionsWidget
1149
 
        .superclass.constructor.apply(this, arguments);
1150
 
};
1151
 
 
1152
 
FormActionsWidget.ATTRS = {
1153
 
    duration: {
1154
 
        value: 1.0
1155
 
    },
1156
 
 
1157
 
    height: {
1158
 
        value: 0
1159
 
    },
1160
 
 
1161
 
    opacity: {
1162
 
        value: 0
1163
 
    }
1164
 
};
1165
 
 
1166
 
 
1167
 
Y.mix(FormActionsWidget, {
1168
 
 
1169
 
    NAME: 'formActionsWidget',
1170
 
 
1171
 
    HTML_PARSER: {
1172
 
        submitButtonNode: "input[type=submit]"
1173
 
    }
1174
 
 
1175
 
});
1176
 
 
1177
 
Y.extend(FormActionsWidget, Y.Widget, {
1178
 
 
1179
 
    initializer: function(config) {
1180
 
        this.client = new Y.lp.client.Launchpad();
1181
 
        this.error_handler = new Y.lp.client.ErrorHandler();
1182
 
        this.error_handler.clearProgressUI = Y.bind(this.hideSpinner, this);
1183
 
        this.error_handler.showError = Y.bind(this.showError, this);
1184
 
        this.submitButtonNode = config.submitButtonNode;
1185
 
        this.spinnerNode = Y.Node.create(
1186
 
            '<img src="/@@/spinner" alt="Loading..." />');
1187
 
    },
1188
 
 
1189
 
    /**
1190
 
     * Show the spinner, and hide the submit button.
1191
 
     *
1192
 
     * @method showSpinner
1193
 
     */
1194
 
    showSpinner: function() {
1195
 
        this.submitButtonNode.replace(this.spinnerNode);
1196
 
    },
1197
 
 
1198
 
    /**
1199
 
     * Hide the spinner, and show the submit button again.
1200
 
     *
1201
 
     * @method hideSpinner
1202
 
     */
1203
 
    hideSpinner: function() {
1204
 
        this.spinnerNode.replace(this.submitButtonNode);
1205
 
    },
1206
 
 
1207
 
    /**
1208
 
     * Display an error.
1209
 
     *
1210
 
     * @method showError
1211
 
     */
1212
 
    showError: function(error) {
1213
 
        Y.Node.create('<p class="error message" />')
1214
 
            .appendTo(this.get("contentBox"))
1215
 
            .set("text", error);
1216
 
    },
1217
 
 
1218
 
    /**
1219
 
     * Remove all errors that have been previously displayed by showError.
1220
 
     *
1221
 
     * @method hideErrors
1222
 
     */
1223
 
    hideErrors: function(error) {
1224
 
        this.get("contentBox").all("p.error.message").remove();
1225
 
    }
1226
 
 
1227
 
});
1228
 
 
1229
 
namespace.FormActionsWidget = FormActionsWidget;
 
17
var widgets = Y.lp.registry.distroseries.widgets;
 
18
var formwidgets = Y.lp.app.formwidgets;
1230
19
 
1231
20
 
1232
21
/**
1234
23
 *
1235
24
 * @class DeriveDistroSeriesActionsWidget
1236
25
 */
1237
 
var DeriveDistroSeriesActionsWidget = function() {
 
26
var DeriveDistroSeriesActionsWidget;
 
27
 
 
28
DeriveDistroSeriesActionsWidget = function() {
1238
29
    DeriveDistroSeriesActionsWidget
1239
30
        .superclass.constructor.apply(this, arguments);
1240
31
};
1245
36
 
1246
37
});
1247
38
 
1248
 
Y.extend(DeriveDistroSeriesActionsWidget, FormActionsWidget, {
 
39
Y.extend(DeriveDistroSeriesActionsWidget, formwidgets.FormActionsWidget, {
1249
40
 
1250
41
    initializer: function(config) {
1251
42
        this.context = config.context;
1333
124
        add_parent_series(result);
1334
125
    };
1335
126
 
1336
 
    parent_picker = Y.lp.app.picker.create('DistroSeriesDerivation', config);
 
127
    var parent_picker =
 
128
        Y.lp.app.picker.create('DistroSeriesDerivation', config);
1337
129
    parent_picker.show();
1338
130
};
1339
131
 
1354
146
 *
1355
147
 * @function setup
1356
148
 */
1357
 
namespace.setup = function() {
1358
 
    var form_actions = namespace.setupWidgets();
1359
 
    namespace.setupInteraction(form_actions);
 
149
namespace.setup = function(cache) {
 
150
    var form_actions = namespace.setupWidgets(cache);
 
151
    namespace.setupInteraction(form_actions, cache);
1360
152
};
1361
153
 
1362
154
/**
1363
155
 * Setup the widgets objects and return the form object.
1364
156
 *
1365
157
 * @function setupWidgets
 
158
 * @param {Object} cache Specify the value cache to use. If not
 
159
 *     specified, LP.cache is used. Intended chiefly for testing.
1366
160
 */
1367
 
namespace.setupWidgets = function() {
 
161
namespace.setupWidgets = function(cache) {
 
162
    if (cache === undefined) { cache = LP.cache; }
 
163
 
1368
164
    var form_container = Y.one("#initseries-form-container");
1369
165
    var form_table = form_container.one("table.form");
1370
166
    var form_table_body = form_table.append(Y.Node.create('<tbody />'));
1377
173
            .set("text", "Add parent series");
1378
174
    add_parent_link.appendTo(form_table_body);
1379
175
    var parents_selection =
1380
 
        new ParentSeriesListWidget()
 
176
        new widgets.ParentSeriesListWidget()
1381
177
            .set("name", "field.parent")
1382
178
            .set("label", "Parent Series:")
1383
179
            .set("description", (
1384
180
                     "Choose and configure the parent series."))
1385
181
            .render(form_table_body);
1386
182
    var architecture_choice =
1387
 
        new ArchitecturesChoiceListWidget()
 
183
        new widgets.ArchitecturesChoiceListWidget()
1388
184
            .set("name", "field.architectures")
1389
185
            .set("label", "Architectures:")
1390
186
            .set("description", (
1394
190
                     "architectures)."))
1395
191
            .render(form_table_body);
1396
192
    var packageset_choice = null;
1397
 
    if (LP.cache['is_first_derivation']) {
 
193
    if (cache.is_first_derivation) {
1398
194
        packageset_choice =
1399
 
            new PackagesetPickerWidget()
 
195
            new widgets.PackagesetPickerWidget()
1400
196
                .set("name", "field.packagesets")
1401
197
                .set("size", 5)
1402
198
                .set("help", {link: '/+help/init-series-packageset-help.html',
1411
207
                .render(form_table_body);
1412
208
    }
1413
209
    var package_copy_options =
1414
 
        new ChoiceListWidget()
 
210
        new formwidgets.ChoiceListWidget()
1415
211
            .set("name", "field.package_copy_options")
1416
212
            .set("type", "radio")
1417
213
            .set("label", "Copy options:")
1424
220
            .render(form_table_body);
1425
221
    var form_actions =
1426
222
        new DeriveDistroSeriesActionsWidget({
1427
 
            context: LP.cache.context,
 
223
            context: cache.context,
1428
224
            srcNode: form_container.one("div.actions"),
1429
225
            deriveFromChoices: parents_selection,
1430
226
            architectureChoice: architecture_choice,
1440
236
 * Setup the interaction between the widgets.
1441
237
 *
1442
238
 * @function setupInteraction
1443
 
 * @param {DeriveDistroSeriesActionsWidget} The form widget containing all
1444
 
 *     the other widgets.
 
239
 * @param {DeriveDistroSeriesActionsWidget} The form widget containing
 
240
 *     all the other widgets.
 
241
 * @param {Object} cache Specify the value cache to use. If not
 
242
 *     specified, LP.cache is used. Intended chiefly for testing.
1445
243
 */
1446
 
namespace.setupInteraction = function(form_actions) {
 
244
namespace.setupInteraction = function(form_actions, cache) {
 
245
    if (cache === undefined) { cache = LP.cache; }
 
246
 
1447
247
    // Wire up the add parent series link.
1448
248
    var link = Y.one('#add-parent-series');
1449
249
    if (Y.Lang.isValue(link)) {
1458
258
        }
1459
259
    });
1460
260
 
1461
 
    if (LP.cache['is_first_derivation']) {
 
261
    if (cache.is_first_derivation) {
1462
262
        // Wire the architecture and packageset pickers to the parent picker.
1463
263
        Y.on('parent_added', function(parent) {
1464
264
            form_actions.architectureChoice.add_distroseries(parent);
1471
271
        });
1472
272
    }
1473
273
    else {
1474
 
        // Disable rebuilding if LP.cache['is_first_derivation'] is false.
 
274
        // Disable rebuilding if cache.is_first_derivation is false.
1475
275
        form_actions.packageCopyOptions.fieldNode
1476
276
            .one('input[value="Copy Source and Rebuild"]')
1477
277
            .set('disabled', 'disabled');
1485
285
            "if you want to use all the available " +
1486
286
            "architectures)."));
1487
287
        form_actions.architectureChoice.add_distroseries(
1488
 
            LP.cache['previous_series']);
 
288
            cache.previous_series);
1489
289
        // Setup the pre-selected parents (parents from the previous series).
1490
 
        var index;
1491
 
        for (index in LP.cache['previous_parents']) {
1492
 
            form_actions.deriveFromChoices.add_parent(
1493
 
                LP.cache['previous_parents'][index]);
1494
 
        }
 
290
        Y.each(
 
291
            cache.previous_parents,
 
292
            form_actions.deriveFromChoices.add_parent,
 
293
            form_actions.deriveFromChoices);
1495
294
    }
1496
295
 
1497
296
    // Wire up the form submission.
1505
304
};
1506
305
 
1507
306
 
1508
 
}, "0.1", {"requires": ["node", "dom", "io", "widget", "lp.client",
1509
 
                        "lazr.anim", "array-extras", "transition",
1510
 
                        "lp.app.picker"]});
 
307
}, "0.1", {"requires": ["node", "dom", "io", "widget", "array-extras",
 
308
                        "transition", "lp.registry.distroseries.widgets",
 
309
                        "lp.app.formwidgets", "lp.app.picker"]});