From 293294f2401f3dac6d07e9af65b5582ba893a777 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Mon, 9 Feb 2015 08:34:37 -0500 Subject: 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. --- js/view/generations.js | 55 ----------- js/view/packageInfo.js | 73 -------------- js/view/packages.js | 256 ++++++++++++++----------------------------------- js/view/ui.js | 75 +++++++++++++++ 4 files changed, 145 insertions(+), 314 deletions(-) delete mode 100644 js/view/generations.js delete mode 100644 js/view/packageInfo.js (limited to 'js/view') 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 -// -// 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 -// . - -(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 -// -// 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 -// . - -(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 @@ // . (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]); + }; })(); -- cgit v1.2.3