summaryrefslogtreecommitdiff
path: root/js/view
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/view
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/view')
-rw-r--r--js/view/generations.js55
-rw-r--r--js/view/packageInfo.js73
-rw-r--r--js/view/packages.js256
-rw-r--r--js/view/ui.js75
4 files changed, 145 insertions, 314 deletions
diff --git a/js/view/generations.js b/js/view/generations.js
deleted file mode 100644
index 73fbce7..0000000
--- a/js/view/generations.js
+++ /dev/null
@@ -1,55 +0,0 @@
-// guix-web - Web interface for GNU Guix
-// Copyright © 2014 David Thompson <davet@gnu.org>
-//
-// This program is free software: you can redistribute it and/or
-// modify it under the terms of the GNU Affero General Public License
-// as published by the Free Software Foundation, either version 3 of
-// the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public
-// License along with this program. If not, see
-// <http://www.gnu.org/licenses/>.
-
-(function(generations) {
- generations.view = function(ctrl) {
- return guix.withLayout([
- guix.ui.headerWithBadge("Generations", ctrl.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", [
- ctrl.generations().map(function(generation) {
- var entries = generation.manifestEntries;
-
- 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);
- }));
- })
- ])
- ])
- ]);
- };
-})(guix.generations);
diff --git a/js/view/packageInfo.js b/js/view/packageInfo.js
deleted file mode 100644
index 687d145..0000000
--- a/js/view/packageInfo.js
+++ /dev/null
@@ -1,73 +0,0 @@
-// guix-web - Web interface for GNU Guix
-// Copyright © 2015 David Thompson <davet@gnu.org>
-//
-// This program is free software: you can redistribute it and/or
-// modify it under the terms of the GNU Affero General Public License
-// as published by the Free Software Foundation, either version 3 of
-// the License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful, but
-// WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public
-// License along with this program. If not, see
-// <http://www.gnu.org/licenses/>.
-
-(function() {
- var packageInfo = guix.packageInfo;
- var spinner = m(".spinner-container", m(".spinner"));
-
- packageInfo.view = function(ctrl) {
- var packages = ctrl.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.withLayout([
- guix.ui.headerWithBadge(ctrl.name, packageCount),
- m("ul.list-unstyled", packages.map(describePackage))
- ]);
- };
-})();
diff --git a/js/view/packages.js b/js/view/packages.js
index fde0fde..245677a 100644
--- a/js/view/packages.js
+++ b/js/view/packages.js
@@ -16,207 +16,91 @@
// <http://www.gnu.org/licenses/>.
(function() {
- var packages = guix.packages;
- var spinner = m(".spinner-container", m(".spinner"));
-
- packages.view = function(ctrl) {
- function renderName(package) {
- var name = package.name;
-
- return m("a", {
- config: m.route,
- href: "/package/".concat(name)
- }, name);
- }
-
- function renderHomepage(package) {
- if(package.homepage) {
- return m("a", { href: package.homepage }, package.homepage);
- } else {
- return "";
- }
- }
-
- function renderInstallLink(package) {
- return m("a", {
- href: "#",
- onclick: function() {
- ctrl.selectedPackage(package);
- ctrl.phase(packages.PHASE_PROMPT);
- return false;
- }
- }, "install");
- }
-
- function renderPackageTable() {
- return m("table.table", [
- m("thead", [
- m("tr", [
- ctrl.columns.map(function(column) {
- return m("th", {
- class: columnHeaderClass(column),
- onclick: function() {
- ctrl.sortBy(column.sortField);
- }
- }, column.header);
- }).concat([m("th", "")])
+ var view = guix.packages.view = {};
+
+ view.installModal = function(package, phase, phaseStream) {
+ function renderPromptModal() {
+ var body = [
+ m("p", "Do you want to install the following packages?"),
+ m("ul", [
+ m("li", [
+ package.name,
+ " ",
+ package.version
])
- ]),
- m("tbody", [
- ctrl.pager().currentPage().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))
- ]);
- })
])
- ]);
- }
-
- function renderPagination() {
- function renderPage(text, opts) {
- return m("li", {
- class: opts.class || "",
- onclick: opts.onclick
- }, m("a", { href: "#" }, text));
- }
-
- return m("div", m("ul.pagination", [
- // Back page
- renderPage("«", {
- class: ctrl.pager().isFirstPage() ? "disabled" : "",
+ ];
+ var buttons = [
+ m(".btn.btn-default", {
+ onclick: guix.withEmit(phaseStream, guix.packages.PHASE_NONE)
+ }, "Cancel"),
+ m(".btn.btn-primary", {
onclick: function() {
- ctrl.pager().previousPage();
- return false;
+ phaseStream.emit(guix.packages.PHASE_DERIVATION);
+ guix.packages.installPackage(package).then(function() {
+ phaseStream.emit(guix.packages.PHASE_SUCCESS);
+ }, function() {
+ phaseStream.emit(guix.packages.PHASE_ERROR);
+ });
}
- })
- ].concat(ctrl.pager().pages.map(function(page, i) {
- // Jump to page
- return renderPage(i + 1, {
- class: ctrl.pager().isCurrentPage(i) ? "active" : "",
- onclick: function() {
- ctrl.pager().gotoPage(i);
- return false;
- }
- });
- })).concat([
- // Forward page
- renderPage("»", {
- class: ctrl.pager().isLastPage() ? "disabled" : "",
- onclick: function() {
- ctrl.pager().nextPage();
- return false;
- }
- })
- ])));
- }
+ }, "Install"),
+ ];
- function renderSearchBox() {
- return m("input.form-control", {
- type: "text",
- placeholder: "Search",
- oninput: m.withAttr("value", function(value) {
- ctrl.searchTerm(value);
- ctrl.doSearch();
- }),
- value: ctrl.searchTerm()
- });
+ return guix.ui.modal("Install Packages", body, buttons);
}
- function columnHeaderClass(column) {
- var sorter = ctrl.sorter();
-
- if(column.sortField === sorter.field) {
- return sorter.isDescending ? "sorter sort-descend" : "sorter sort-ascend";
- }
+ function renderDerivationModal() {
+ var body = [
+ m("p", [
+ "Installing ",
+ package.name,
+ " ",
+ package.version,
+ "..."
+ ]),
+ m(".progress", [
+ m(".progress-bar.progress-bar-striped.active", {
+ role: "progressbar",
+ style: { width: "100%" }
+ })
+ ])
+ ];
+ var buttons = m(".btn.btn-danger", {
+ onclick: guix.withEmit(phaseStream, guix.packages.PHASE_NONE)
+ }, "Abort");
- return "sorter";
+ return guix.ui.modal("Install Packages", body, buttons);
}
- function renderModal() {
- function renderBody() {
- switch(ctrl.phase()) {
- case packages.PHASE_PROMPT:
- return [
- m("p", "Do you want to install the following packages?"),
- m("ul", [
- m("li", [
- ctrl.selectedPackage().name,
- " ",
- ctrl.selectedPackage().version
- ])
- ])
- ];
- case packages.PHASE_DERIVATION:
- return [
- m("p", [
- "Installing ",
- ctrl.selectedPackage().name,
- " ",
- ctrl.selectedPackage().version,
- "..."
- ]),
- m(".progress", [
- m(".progress-bar.progress-bar-striped.active", {
- role: "progressbar",
- style: { width: "100%" }
- })
- ])
- ];
- case packages.PHASE_SUCCESS:
- return m(".alert.alert-success", "Installation complete!");
- case packages.PHASE_ERROR:
- return m(".alert.alert-danger", "Installation failed!");
- }
+ function renderSuccessModal() {
+ var body = m(".alert.alert-success", "Installation complete!");
+ var buttons = m(".btn.btn-primary", {
+ onclick: guix.withEmit(phaseStream, guix.packages.PHASE_NONE)
+ }, "Close");
- return null;
- }
-
- function renderButtons() {
- switch(ctrl.phase()) {
- case packages.PHASE_PROMPT:
- return [
- m(".btn.btn-default", "Cancel"),
- m(".btn.btn-primary", {
- onclick: function() {
- ctrl.installSelectedPackage();
- m.redraw();
- }
- }, "Install"),
- ];
- case packages.PHASE_DERIVATION:
- return m(".btn.btn-danger", "Abort");
- case packages.PHASE_SUCCESS:
- case packages.PHASE_ERROR:
- return m(".btn.btn-primary", {
- onclick: function() {
- ctrl.phase(packages.PHASE_NONE);
- }
- }, "Close");
- }
+ return guix.ui.modal("Install Packages", body, buttons);
+ }
- return null;
- }
+ function renderErrorModal() {
+ var body = m(".alert.alert-danger", "Installation failed!");
+ var buttons = m(".btn.btn-primary", {
+ onclick: guix.withEmit(phaseStream, guix.packages.PHASE_NONE)
+ }, "Close");
- if(ctrl.phase() != packages.PHASE_NONE) {
- return guix.ui.modal("Install Packages",
- renderBody(),
- renderButtons());
- }
+ return guix.ui.modal("Install Packages", body, buttons);
+ }
- return null;
+ switch(phase) {
+ case guix.packages.PHASE_PROMPT:
+ return renderPromptModal();
+ case guix.packages.PHASE_DERIVATION:
+ return renderDerivationModal();
+ case guix.packages.PHASE_SUCCESS:
+ return renderSuccessModal();
+ case guix.packages.PHASE_ERROR:
+ return renderErrorModal();
}
- return guix.withLayout(_.isEmpty(ctrl.packages()) ? spinner : [
- guix.ui.headerWithBadge("Packages", ctrl.packageCount()),
- renderModal(),
- renderSearchBox(),
- renderPackageTable(),
- renderPagination()
- ]);
+ return null;
};
})();
diff --git a/js/view/ui.js b/js/view/ui.js
index a528bee..859fae6 100644
--- a/js/view/ui.js
+++ b/js/view/ui.js
@@ -58,4 +58,79 @@
return "";
}
};
+
+ ui.paginate = function(currentPage, numPages, maxShown, emitter) {
+ function renderPage(text, attrs) {
+ attrs = attrs || {};
+ return m("li", attrs, m("a", { href: "#" }, text));
+ }
+
+ var ellipsis = renderPage("…", { class: "disabled" });
+ var start = currentPage - currentPage % maxShown;
+ var lastPage = numPages - 1;
+ var firstPrevClass = currentPage === 0 ? "disabled" : "";
+ var lastNextClass = currentPage === lastPage ? "disabled" : "";
+ var range = _.range(start, Math.min(start + maxShown, numPages));
+
+ return m("div", m("ul.pagination", [
+ // Back page
+ renderPage("First", {
+ class: firstPrevClass,
+ onclick: function() {
+ emitter.emit(0);
+ }
+ }),
+ // Jump to first page
+ renderPage("Previous", {
+ class: firstPrevClass,
+ onclick: function() {
+ if(currentPage > 0) {
+ emitter.emit(currentPage - 1);
+ }
+ }
+ }),
+ // Display ellipsis if there are hidden pages.
+ start > 0 ? ellipsis : ""
+ ].concat(range.map(function(i) {
+ // Jump to page
+ var attrs = {
+ class: i === currentPage ? "active" : "",
+ onclick: function() {
+ emitter.emit(i);
+ }
+ };
+ return renderPage(i + 1, attrs);
+ })).concat([
+ // Display ellipsis if there are hidden pages.
+ start + maxShown < numPages ? ellipsis : "",
+ // Forward page
+ renderPage("Next", {
+ class: lastNextClass,
+ onclick: function() {
+ if(currentPage < lastPage) {
+ emitter.emit(currentPage + 1);
+ }
+ }
+ }),
+ // Jump to last page
+ renderPage("Last", {
+ class: lastNextClass,
+ onclick: function() {
+ emitter.emit(lastPage);
+ }
+ })
+ ])));
+ };
+
+ ui.spinner = m(".spinner", [
+ m(".rect1"),
+ m(".rect2"),
+ m(".rect3"),
+ m(".rect4"),
+ m(".rect5")
+ ]);
+
+ ui.spinUntil = function(ob) {
+ return K.merge([K.constant(ui.spinner), ob]);
+ };
})();