summaryrefslogtreecommitdiff
path: root/js/controller
diff options
context:
space:
mode:
authorDavid Thompson <dthompson2@worcester.edu>2015-02-09 08:34:37 -0500
committerDavid Thompson <dthompson2@worcester.edu>2015-02-09 08:34:37 -0500
commit293294f2401f3dac6d07e9af65b5582ba893a777 (patch)
tree3f2b64f314d692e2359a613e3f0e2c5dd99e7f71 /js/controller
parent6546b8cae1aead78da45dd269d13219257af04ab (diff)
js: Overhaul UI with FRP!
* css/guix.css: New loading spinner. * js/lib/kefir.js: New file. * js/model/packages.js (guix.packages.Packages): Cache result. (guix.packages.Sorter, guix.packages.Pager): Delete. (guix.packages.installPackage): New function. * js/utils.js (K): New variable. (guix.withEmit, guix.withEmitAttr, guix.makeModule): New functions. * js/view/ui.js (guix.ui.paginate, guix.ui.spinUntil): New functions. (guix.ui.spinner): New variable. * js/controller/generations.js: Rewrite. * js/controller/packageInfo.js: Rewrite * js/controller/packages.js: Rewrite. * js/view/packages.js: Rewrite. * js/view/generations.js: Delete. * js/view/packageInfo.js: Delete. * js/routes.js: Use new modules. * guix/web/view/html.scm (javascripts): Update list.
Diffstat (limited to 'js/controller')
-rw-r--r--js/controller/generations.js49
-rw-r--r--js/controller/packageInfo.js59
-rw-r--r--js/controller/packages.js246
3 files changed, 257 insertions, 97 deletions
diff --git a/js/controller/generations.js b/js/controller/generations.js
index 011dfa2..2b17a59 100644
--- a/js/controller/generations.js
+++ b/js/controller/generations.js
@@ -15,16 +15,43 @@
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
-(function(generations) {
- generations.controller = (function() {
- function controller() {
- this.generations = m.prop([]);
+guix.generations.controller = function() {
+ var generations = K.fromPromise(guix.generations.Generations());
- generations.Generations()
- .then(this.generations)
- .then(m.redraw);
- }
+ return guix.ui.spinUntil(generations.map(function(generations) {
+ return [
+ guix.ui.headerWithBadge("Generations", generations.length),
+ m("table.table.table-bordered", [
+ m("thead", m("tr", [
+ m("th", "#"),
+ m("th", "Name"),
+ m("th", "Version"),
+ m("th", "Output"),
+ m("th", "Location")
+ ])),
+ m("tbody", [
+ generations.map(function(generation) {
+ var entries = generation.manifestEntries;
- return controller;
- })();
-})(guix.generations);
+ function renderRow(entry, isFirst) {
+ return m("tr", [
+ isFirst ? m("td", {
+ rowspan: entries.length
+ }, m("strong", generation.number)) : null,
+ m("td", entry.name),
+ m("td", entry.version),
+ m("td", entry.output),
+ m("td", entry.location)
+ ]);
+ }
+
+ return [renderRow(entries[0], true)]
+ .concat(entries.slice(1).map(function (entry) {
+ return renderRow(entry, false);
+ }));
+ })
+ ])
+ ])
+ ];
+ })).map(guix.withLayout);
+};
diff --git a/js/controller/packageInfo.js b/js/controller/packageInfo.js
index ebd6a51..bb6c0b4 100644
--- a/js/controller/packageInfo.js
+++ b/js/controller/packageInfo.js
@@ -16,10 +16,61 @@
// <http://www.gnu.org/licenses/>.
(function() {
- var packageInfo = guix.packageInfo = {};
+ guix.packageInfo = {};
- packageInfo.controller = function() {
- this.name = m.route.param("name");
- this.packages = guix.packages.PackagesByName(this.name);
+ guix.packageInfo.controller = function() {
+ var name = m.route.param("name");
+ var packages = K.fromPromise(guix.packages.PackagesByName(name));
+
+ // View.
+ return guix.ui.spinUntil(packages.map(function(packages) {
+ var packageCount = (function() {
+ var count = packages.length;
+ var units = count > 1 ? " versions" : " version";
+
+ return count.toString().concat(units);
+ })();
+
+ function describeInputs(inputs, description) {
+ return _.isEmpty(inputs) ? [] : [
+ m("dt", description),
+ m("dd", m("ul", inputs.map(function(p) {
+ return m("li", m("a", {
+ config: m.route,
+ href: "/package/".concat(p.name)
+ }, p.name.concat(" ").concat(p.version)));
+ })))
+ ];
+ }
+
+ function describePackage(package) {
+ var baseDescription = [
+ m("dt", "Version"),
+ m("dd", package.version),
+ m("dt", "Synopsis"),
+ m("dd", package.synopsis),
+ m("dt", "Description"),
+ m("dd", package.description),
+ m("dt", "License"),
+ m("dd", guix.ui.licenseList(package))
+ ];
+ var inputs = describeInputs(package.inputs, "Inputs");
+ var nativeInputs = describeInputs(package.nativeInputs,
+ "Native Inputs");
+ var propagatedInputs = describeInputs(package.propagatedInputs,
+ "Propagated Inputs");
+ return m("li", m("dl", _.flatten([
+ baseDescription,
+ inputs,
+ nativeInputs,
+ propagatedInputs
+ ], true)));
+ }
+
+ return [
+ guix.ui.headerWithBadge(name, packageCount),
+ m("ul.list-unstyled", packages.map(describePackage))
+ ];
+ })).map(guix.withLayout);
};
})();
diff --git a/js/controller/packages.js b/js/controller/packages.js
index 9cac565..6a3ad75 100644
--- a/js/controller/packages.js
+++ b/js/controller/packages.js
@@ -18,99 +18,181 @@
(function() {
var packages = guix.packages;
- packages.controller = (function() {
+ packages.controller = function() {
var PAGE_SIZE = 20;
-
- function Pager(items) {
- return new packages.Pager(items, PAGE_SIZE);
- }
-
- function controller() {
- var self = this;
-
- this.searchTerm = m.prop("");
- this.columns = [
- { header: "Name", sortField: "name" },
- { header: "Version", sortField: "version" },
- { header: "Synopsis", sortField: "synopsis" },
- { header: "Home Page", sortField: "homepage" }, {
- header: "License",
- sortField: function(package) {
- if(_.isArray(package.license)) {
- // Concatenate all license names together for sorting.
- return package.license.reduce(function(memo, l) {
- return memo.concat(l.name);
- }, "");
- }
-
- return package.license.name;
- }
- }
- ];
- this.sorter = m.prop(new packages.Sorter("name"));
- this.pager = m.prop(Pager([]));
- this.phase = m.prop(packages.PHASE_NONE);
- this.selectedPackage = m.prop(null);
- this.packages = m.prop([]);
-
- packages.Packages()
- .then(this.packages)
- .then(function(packages) {
- // All packages are visible initially
- self.sortAndPage(packages);
- })
- .then(m.redraw);
- };
-
- controller.prototype.packageCount = function() {
- return _.chain(this.pager().pages)
- .pluck('length')
- .reduce(guix.add, 0)
- .value();
- };
-
- controller.prototype.sortAndPage = function(packages) {
- this.pager(Pager(this.sorter().sort(packages)));
- };
-
- controller.prototype.doSearch = function() {
- var regexp = new RegExp(this.searchTerm(), "i");
- var filteredPackages = this.packages().filter(function(package) {
+ // Throttle search to twice per second maximum.
+ var SEARCH_THROTTLE = 500;
+
+ var packages = K.fromPromise(guix.packages.Packages());
+ var searchTerm = K.emitter();
+ var sorterStream = K.emitter();
+ var pageIndex = K.emitter();
+ var phaseStream = K.emitter();
+ var selectedPackageStream = K.emitter();
+
+ var filteredPackages = K.combine([
+ packages,
+ searchTerm.throttle(SEARCH_THROTTLE).toProperty("")
+ ], function(packages, search) {
+ var regexp = new RegExp(search === "" ? ".*" : search, "i");
+
+ return packages.filter(function(package) {
return regexp.test(package.name) ||
regexp.test(package.synopsis);
});
+ });
+
+ var sorter = sorterStream.toProperty({
+ field: "name",
+ reverse: false
+ });
+
+ var sortFields = {
+ license: function(package) {
+ if(_.isArray(package.license)) {
+ // Concatenate all license names together for sorting.
+ return package.license.reduce(function(memo, l) {
+ return memo.concat(l.name);
+ }, "");
+ }
- this.sortAndPage(filteredPackages);
+ return package.license.name;
+ }
};
- controller.prototype.sortBy = function(field) {
- if(this.sorter().field === field) {
- // Reverse sort order if the field is the same as before.
- this.sorter(this.sorter().reverse());
- } else {
- this.sorter(new packages.Sorter(field));
+ var sortedPackages = K.combine([
+ filteredPackages,
+ sorter
+ ], function(packages, sorter) {
+ var field = sorter.field;
+ var sorted = _.sortBy(packages, sortFields[field] || field);
+
+ return sorter.reverse ? sorted.reverse() : sorted;
+ });
+
+ var pages = sortedPackages.map(function(packages) {
+ return guix.chunk(packages, PAGE_SIZE);
+ }).toProperty([[]]);
+
+ var page = K.combine([
+ pages,
+ pageIndex.toProperty(0)
+ ], function(pages, i) {
+ return {
+ index: i,
+ packages: pages[i]
+ };
+ });
+
+ var phase = phaseStream.toProperty(guix.packages.PHASE_NONE);
+
+ return guix.ui.spinUntil(K.combine([
+ packages,
+ pages,
+ page,
+ searchTerm.toProperty(""),
+ sorter,
+ phase,
+ selectedPackageStream.toProperty(null)
+ ], function(packages, pages, page, search, sorter, phase, selectedPackage) {
+ function renderName(package) {
+ var name = package.name;
+
+ return m("a", {
+ config: m.route,
+ href: "/package/".concat(name)
+ }, name);
}
- this.doSearch();
- };
+ function renderHomepage(package) {
+ if(package.homepage) {
+ return m("a", { href: package.homepage }, package.homepage);
+ } else {
+ return "";
+ }
+ }
- controller.prototype.installSelectedPackage = function() {
- var self = this;
+ function renderHeader(title, field) {
+ var isCurrentSorter = sorter.field === field;
+ var sorterClass = (function() {
+ if(isCurrentSorter) {
+ return sorter.reverse ?
+ "sorter sort-descend" :
+ "sorter sort-ascend";
+ }
- this.phase(packages.PHASE_DERIVATION);
+ return "sorter";
+ })();
+
+ return m("th", {
+ class: sorterClass,
+ onclick: function() {
+ if(isCurrentSorter) {
+ sorterStream.emit({
+ field: field,
+ reverse: !sorter.reverse
+ });
+ } else {
+ sorterStream.emit({
+ field: field,
+ reverse: false
+ });
+ }
+ }
+ }, title);
+ }
- m.request({
- method: "POST",
- url: "/packages/"
- .concat(this.selectedPackage().name)
- .concat("/install")
- }).then(function() {
- self.phase(packages.PHASE_SUCCESS);
- }, function() {
- self.phase(packages.PHASE_ERROR);
- });
- };
+ function renderInstallLink(package) {
+ return m("a", {
+ href: "#",
+ onclick: function() {
+ selectedPackageStream.emit(package);
+ phaseStream.emit(guix.packages.PHASE_PROMPT);
+ }
+ }, "install");
+ }
- return controller;
- })();
+ var pagination = guix.ui.paginate(page.index, pages.length,
+ 10, pageIndex);
+
+ return [
+ guix.ui.headerWithBadge("Packages", packages.length),
+ // Installation modal
+ guix.packages.view.installModal(selectedPackage, phase, phaseStream),
+ // Search box
+ m("input.form-control", {
+ type: "text",
+ placeholder: "Search",
+ oninput: guix.withEmitAttr("value", searchTerm)
+ }),
+ pagination,
+ // Package table
+ m("table.table", [
+ m("thead", [
+ m("tr", [
+ renderHeader("Name", "name"),
+ renderHeader("Version", "version"),
+ renderHeader("Synopsis", "synopsis"),
+ renderHeader("Home page", "homepage"),
+ renderHeader("License", "license"),
+ m("th", "")
+ ])
+ ]),
+ m("tbody", [
+ page.packages.map(function(package) {
+ return m("tr", [
+ m("td", renderName(package)),
+ m("td", package.version),
+ m("td", package.synopsis),
+ m("td", renderHomepage(package)),
+ m("td", guix.ui.licenseList(package)),
+ m("td", renderInstallLink(package))
+ ]);
+ })
+ ])
+ ]),
+ pagination
+ ];
+ })).map(guix.withLayout);
+ };
})();