From 82d4ae2eb7be14e7057fccf9133d3e276f57caa3 Mon Sep 17 00:00:00 2001 From: Martchus Date: Wed, 10 Feb 2016 21:09:20 +0100 Subject: [PATCH] added tabbing --- CMakeLists.txt | 6 +- alpm/packagefinder.cpp | 23 +++++-- alpm/packagefinder.h | 9 ++- alpm/resolvebuildorder.cpp | 47 ++++++++++--- alpm/resolvebuildorder.h | 2 +- alpm/utilities.cpp | 6 -- repoindex.pro | 2 +- web/css/.core.css.swp | Bin 12288 -> 0 bytes web/css/core.css | 12 +++- web/index.html | 5 +- web/js/entrymanagement.js | 33 ++++++--- web/js/packagemanagement.js | 17 ++--- web/js/pagemanagement.js | 31 ++++++++- web/js/proto.js | 14 ++-- web/js/repomanagement.js | 17 ++--- web/js/tabbing.js | 132 ++++++++++++++++++++++++++++++++++++ web/js/utils.js | 43 +++++++++--- 17 files changed, 324 insertions(+), 75 deletions(-) delete mode 100644 web/css/.core.css.swp create mode 100644 web/js/tabbing.js diff --git a/CMakeLists.txt b/CMakeLists.txt index 26591b6..9c0a267 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,7 +62,6 @@ set(WEB_FILES web/3rdparty/bootstrap/js/npm.js web/3rdparty/bootstrap_dropdowns_enhancement/css/dropdowns-enhancement.min.css web/3rdparty/bootstrap_dropdowns_enhancement/js/dropdowns-enhancement.js - web/css/.core.css.swp web/css/core.css web/css/dashboard.css web/index.html @@ -75,6 +74,7 @@ set(WEB_FILES web/js/pagination.js web/js/proto.js web/js/repomanagement.js + web/js/tabbing.js web/js/utils.js ) @@ -86,7 +86,7 @@ set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") set(META_APP_DESCRIPTION "Arch Linux repository browser") set(META_VERSION_MAJOR 0) set(META_VERSION_MINOR 0) -set(META_VERSION_PATCH 1) +set(META_VERSION_PATCH 2) # stringification of meta data set(META_PROJECT_NAME_STR "\"${META_PROJECT_NAME}\"") @@ -140,7 +140,7 @@ add_definitions( ) # executable and linking -add_executable(${META_PROJECT_NAME} ${HEADER_FILES} ${SRC_FILES} ${RES_FILES}) +add_executable(${META_PROJECT_NAME} ${HEADER_FILES} ${SRC_FILES} ${WEB_FILES} ${RES_FILES}) target_link_libraries(${META_PROJECT_NAME} c++utilities alpm Qt5::Core Qt5::Concurrent Qt5::Network Qt5::WebSockets KF5::Archive) set_target_properties(${META_PROJECT_NAME} PROPERTIES CXX_STANDARD 11 diff --git a/alpm/packagefinder.cpp b/alpm/packagefinder.cpp index 17e61ac..22264ef 100644 --- a/alpm/packagefinder.cpp +++ b/alpm/packagefinder.cpp @@ -7,18 +7,27 @@ namespace RepoIndex { -PackageFinder::PackageFinder(Manager &manager, const QList &dependencies, QObject *parent) - : QObject(parent), - m_remainingReplies(0) +PackageFinder::PackageFinder(Manager &manager, const QList &dependencies, bool sourcesRequired, QObject *parent) : + QObject(parent), + //m_sourcesRequired(sourcesRequired), + m_remainingReplies(0) { QStringList toRequest; - for(const auto &dependency : dependencies) { - if(auto *pkg = manager.packageProviding(dependency)) { - m_results << pkg; - } else { + if(!sourcesRequired) { + for(const auto &dependency : dependencies) { + if(auto *pkg = manager.packageProviding(dependency)) { + m_results << pkg; + } else { + toRequest << dependency.name; + } + } + } else { + toRequest.reserve(dependencies.size()); + for(const auto &dependency : dependencies) { toRequest << dependency.name; } } + if(manager.userRepository()) { QReadLocker locker(manager.userRepository()->lock()); if(auto *reply = manager.userRepository()->requestFullPackageInfo(toRequest)) { diff --git a/alpm/packagefinder.h b/alpm/packagefinder.h index d157d39..fa3d469 100644 --- a/alpm/packagefinder.h +++ b/alpm/packagefinder.h @@ -13,8 +13,9 @@ class PackageFinder : public QObject { Q_OBJECT public: - explicit PackageFinder(Manager &manager, const QList &dependencies, QObject *parent = nullptr); + explicit PackageFinder(Manager &manager, const QList &dependencies, bool sourcesRequired = false, QObject *parent = nullptr); + void setSourcesRequired(bool sourcesRequired); const QList results() const; bool areAllResultsAvailable() const; Package *packageProviding(const Dependency &dependency); @@ -26,10 +27,16 @@ private slots: void addResults(); private: + //bool m_sourcesRequired; int m_remainingReplies; QList m_results; }; +//void PackageFinder::setSourcesRequired(bool sourcesRequired) +//{ +// return m_sourcesRequired; +//} + inline const QList PackageFinder::results() const { return m_results; diff --git a/alpm/resolvebuildorder.cpp b/alpm/resolvebuildorder.cpp index 6d8a320..6ee332c 100644 --- a/alpm/resolvebuildorder.cpp +++ b/alpm/resolvebuildorder.cpp @@ -31,6 +31,8 @@ public: void setName(const QString &name); const QSet deps() const; void addDep(TaskInfo *dep); + bool areAllDepsAdded() const; + void setAllDepsAdded(); bool isDone() const; bool isVisited() const; bool isOnlyDependency() const; @@ -50,6 +52,7 @@ public: private: QString m_name; QSet m_deps; + bool m_allDepsAdded; bool m_done; bool m_visited; bool m_onlyDep; @@ -65,6 +68,7 @@ private: inline TaskInfo::TaskInfo(const QString &name, bool onlyDependency, const QSet &deps) : m_name(name), m_deps(deps), + m_allDepsAdded(false), m_done(false), m_visited(false), m_onlyDep(onlyDependency), @@ -106,6 +110,22 @@ inline void TaskInfo::addDep(TaskInfo *dep) m_deps << dep; } +/*! + * \brief Returns whether setAllDepsAdded() has been called for this task. + */ +bool TaskInfo::areAllDepsAdded() const +{ + return m_allDepsAdded; +} + +/*! + * \brief Sets that all dependencies have been added for this task. + */ +void TaskInfo::setAllDepsAdded() +{ + m_allDepsAdded = true; +} + /*! * \brief Returns whether the task already has been added to the resulting list of tasks. * \sa add() @@ -211,7 +231,11 @@ inline void TaskInfo::addRequiredFor(const Dependency &dependency) void TaskInfo::add(QList &results) { if(!isDone()) { + if(name() == "mingw-w64-harfbuzz") { + cout << "harfbuzz" << endl; + } if(isVisited()) { + cout << "cyclic dependency: " << name().toStdString() << endl; // cyclic dependency if(isOnlyDependency() || isBinaryAvailable()) { // if this is only a dependency (which we don't want to build) don't care about it @@ -355,7 +379,7 @@ void BuildOrderResolver::findDependencies() { // find specified packages and their dependencies for(int i = 0, size = m_tasks.size(); i != size; ++i) { - if(!addDependenciesToTask(m_tasks.at(i))) { + if(!addDependenciesToTask(m_tasks[i])) { return; } } @@ -428,10 +452,10 @@ void BuildOrderResolver::resolve() /*! * \brief Returns the package for the specified \a dependency or nullptr if no package could be found. */ -Package *BuildOrderResolver::findPackageForDependency(const Dependency &dependency) +Package *BuildOrderResolver::findPackageForDependency(const Dependency &dependency, bool sourceRequired) { Package *pkg; - if((pkg = m_manager.packageProviding(dependency))) { + if(!sourceRequired && (pkg = m_manager.packageProviding(dependency))) { return pkg; } else if(m_finder && (pkg = m_finder->packageProviding(dependency))) { return pkg; @@ -445,12 +469,12 @@ Package *BuildOrderResolver::findPackageForDependency(const Dependency &dependen bool BuildOrderResolver::addDependenciesToTask(TaskInfo *task) { // check whether dependencies have already been added - if(task->associatedPackage()) { + if(task->areAllDepsAdded()) { // package associated -> dependencies already added return true; } Dependency dep(task->name(), QString()); - if(const auto pkg = findPackageForDependency(dep)) { + if(const auto pkg = findPackageForDependency(dep, !task->isOnlyDependency())) { task->addRequiredFor(dep); if(task->associatePackage(pkg)) { if(pkg->repository()->isSourceOnly()) { @@ -461,6 +485,7 @@ bool BuildOrderResolver::addDependenciesToTask(TaskInfo *task) task->setBinaryAvailable(true); } // add dependencies to task + task->setAllDepsAdded(); if(!addDependenciesToTask(task, pkg->allDependencies())) { addError(QStringLiteral("Can not add dependencies of the dependency \"") % dep.toString() % QStringLiteral("\".")); m_hasFinished = true; @@ -493,9 +518,12 @@ bool BuildOrderResolver::addDependenciesToTask(TaskInfo *task, const QListname() : dep.name; auto *depTask = TaskInfo::find(m_tasks, dep.name); + auto *depPkg = findPackageForDependency(dep, depTask && !depTask->isOnlyDependency()); + if(depPkg && dep.name == "mingw-w64-harfbuzz") { + cout << "processing harfbuzz" << endl; + } + const QString taskName = depPkg ? depPkg->name() : dep.name; bool newTask; if(depTask) { // we've already added a task for this dependency @@ -517,8 +545,9 @@ bool BuildOrderResolver::addDependenciesToTask(TaskInfo *task, const QListsetBinaryAvailable(true); } - if(newTask) { + if(!depTask->areAllDepsAdded()) { // add dependencies of the dependency + depTask->setAllDepsAdded(); if(!addDependenciesToTask(depTask, depPkg->allDependencies())) { addError(QStringLiteral("Can not add dependencies of the dependency \"") % dep.toString() % QStringLiteral("\".")); return false; @@ -561,7 +590,7 @@ void BuildOrderResolver::requestDependenciesToBeRequested() } // do requests (using package finder) - m_finder = make_unique(m_manager, m_dependenciesToBeRequested); + m_finder = make_unique(m_manager, m_dependenciesToBeRequested, true); // all dependencies requested -> clear dependencies to be requested m_dependenciesToBeRequested.clear(); diff --git a/alpm/resolvebuildorder.h b/alpm/resolvebuildorder.h index dc9f854..e7469fe 100644 --- a/alpm/resolvebuildorder.h +++ b/alpm/resolvebuildorder.h @@ -45,7 +45,7 @@ protected: private: void addError(const QString &errorMessage); - Package *findPackageForDependency(const Dependency &dependency); + Package *findPackageForDependency(const Dependency &dependency, bool sourceRequired); bool addDependenciesToTask(TaskInfo *task); bool addDependenciesToTask(TaskInfo *task, const QList *> &dependencies); void requestDependenciesToBeRequested(); diff --git a/alpm/utilities.cpp b/alpm/utilities.cpp index 78b0f5b..c036e58 100644 --- a/alpm/utilities.cpp +++ b/alpm/utilities.cpp @@ -58,12 +58,6 @@ QJsonArray sigLevelStrings(alpm_siglevel_t sigLevel) if(sigLevel & ALPM_SIG_DATABASE_UNKNOWN_OK) { options << QStringLiteral("database unknown ok"); } - if(sigLevel & ALPM_SIG_PACKAGE_SET) { - options << QStringLiteral("package set"); - } - if(sigLevel & ALPM_SIG_PACKAGE_TRUST_SET) { - options << QStringLiteral("package trust set"); - } if(sigLevel & ALPM_SIG_USE_DEFAULT) { options << QStringLiteral("use default"); } diff --git a/repoindex.pro b/repoindex.pro index 914453b..4ee7eb9 100644 --- a/repoindex.pro +++ b/repoindex.pro @@ -4,7 +4,7 @@ appname = "Repository Index" appauthor = Martchus appurl = "https://github.com/$${appauthor}/$${projectname}" QMAKE_TARGET_DESCRIPTION = "Provides a web interface to browse Arch Linux package repositories." -VERSION = 0.0.1 +VERSION = 0.0.2 # include ../../common.pri when building as part of a subdirs project; otherwise include general.pri !include(../../common.pri) { diff --git a/web/css/.core.css.swp b/web/css/.core.css.swp deleted file mode 100644 index e07f128de6dcc23df3a27c6fe5ae6221ea47b7dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2KZ_GV7>DQ1`cypZUk!o;H=A4(Wv|jsP^;5Qgvst?cVsiO%uMcb9%3U{TiDnL zr+xxU!B(;I3t0FCEVLH7)y!-xn+RUhb=lV^$+`B07oD{-5|Gsrh zyc2iVg>cq}_V{nlM7oeyQ=fR{wv}5Np1P??C#ujg9qU^8jkIyLbaFD$rfm23RUUl0 zqed-@WZD#Jmge54xpk_C&C>Mz0~=ri%M47zIJvSWh8u%^8`3#@hSpErUKY*gY=8~0 z0XDz}*Z><~18jf|{DTHUvmzd0uGKgj#dYPt8ee%~18jf|umLu}2G{@_U;}J`4X^<= zzy^+>0a*y~X-$awIGV@*{}gBVaYBf1;4AnH-h;Q`HFyd3!3*#VJOz)z6nJnC+yXbi zRnP_J!D+AxzM$4e@BzF5ufRTd0t}eRI5&{eV|Y&S&+;si@W5sC)%%aTU(b=XE+=l78|2* z553eT8QBo*B%@3HX10lsN7fa}{XQNvGxE091$C4{X`-<;A@TZw%4o3BKX*ulMHlEN z=v>x`taXXjgKc6gnvOj*vM6wyJ)>b;d_ES1;sV9pudp3mgbL;Q2g>aU>ZX9US|MJf zT`?CB(t<8 diff --git a/web/css/core.css b/web/css/core.css index 0d032db..0e72b4d 100644 --- a/web/css/core.css +++ b/web/css/core.css @@ -50,7 +50,7 @@ span.glyphicon { background-color: #f9f9db!important; cursor: pointer; } -.info-container th, .info-container td { +.info-table th, .info-table td { border: none!important; font-size: 90%; padding: 5px; @@ -92,6 +92,16 @@ span.glyphicon { border-bottom: 5px solid #337ab7; } +/* + * Tabbing + */ +.tab-content > div { + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + border-bottom: 1px solid #ddd; + padding: 5px; +} + /* * Package info */ diff --git a/web/index.html b/web/index.html index c762c0a..76f8105 100644 --- a/web/index.html +++ b/web/index.html @@ -140,7 +140,7 @@
-
+
@@ -180,7 +180,7 @@
-
+
@@ -251,6 +251,7 @@ + diff --git a/web/js/entrymanagement.js b/web/js/entrymanagement.js index aceb6fb..06c7aa1 100644 --- a/web/js/entrymanagement.js +++ b/web/js/entrymanagement.js @@ -44,6 +44,19 @@ }; this.updateEnabled = function() {}; + + this.contained = function(names, searchDescription) { + for(var i = 0, len = names.length; i < len; ++i) { + if(this.name.contains(names[i])) { + return true; + } else if(searchDescription && this.info.desc && this.info.desc.contains(names[i])) { + // this doesn't work yet because only descriptions of entries which have already + // been shown are available + return true; + } + } + return false; + }; }; repoindex.EntryManager = function(EntryType, entryContainer, pagination) { @@ -62,7 +75,9 @@ // stuff for the filter this.filteredEntries = []; this.filterName = undefined; + this.filterNames = undefined; this.filterNameExact = false; + this.filterNameInDesc = false; this.filterRepos = undefined; this.filterDescr = undefined; this.customSelection = undefined; @@ -79,21 +94,19 @@ // define default filter predicate this.filterPred = function(entry) { - return (!this.filterName || (this.filterNameExact ? entry.name === this.filterName : entry.name.contains(this.filterName))) + return (!this.filterNames || this.filterNames.length === 0 || (this.filterNameExact ? this.filterNames.contains(entry.name) : entry.contained(this.filterNames, this.filterNameInDesc))) && (!this.filterRepos || this.filterRepos.contains(entry.info.repo)); }; // provide functions to apply filter and upgrade UI this.applyFilter = function() { - //if(this.customSelection) { - // this.filteredEntries = this.customSelection; - //} else { - if((this.filterName || this.filterRepos) && this.filterPred) { - this.filteredEntries = this.relevantEntries().filter(this.filterPred, this); - } else { - this.filteredEntries = this.relevantEntries(); - } - //} + if((this.filterName || this.filterRepos) && this.filterPred) { + // split filterName into filterNames + this.filterNames = this.filterName ? this.filterName.split(" ") : undefined; + this.filteredEntries = this.relevantEntries().filter(this.filterPred, this); + } else { + this.filteredEntries = this.relevantEntries(); + } // upgrade pagination (the pageSelected method defined above will be invoked here automatically) this.pagination.entryCount = this.filteredEntries.length; this.pagination.update(); diff --git a/web/js/packagemanagement.js b/web/js/packagemanagement.js index d855210..08499db 100644 --- a/web/js/packagemanagement.js +++ b/web/js/packagemanagement.js @@ -16,8 +16,8 @@ if(color) { this.rowElement.style.backgroundColor = color; } - this.rowElement.onclick = function() { - repoindex.pageManager.packageManager.showPackageInfoForIndex(this.entry.index); + this.rowElement.onclick = function(e) { + repoindex.pageManager.packageManager.showPackageInfoForIndex(this.entry.index, typeof e === "object" && e.button === 1); }; this.initTableRow = function() { @@ -95,13 +95,13 @@ this.infoBox = document.getElementById("packages_info"); - this.showPackageInfo = function(repo, name) { + this.showPackageInfo = function(repo, name, newTab) { var determineEntry = function() { var res = repoindex.pageManager.packageManager.entries.filter(function(entry) { return entry.info.repo === repo && entry.info.name === name; }); if(res.length > 0) { - repoindex.pageManager.packageManager.showPackageInfoForIndex(res[0].index); + repoindex.pageManager.packageManager.showPackageInfoForIndex(res[0].index, newTab); } else { repoindex.pageManager.packageManager.showPackageNotFound(repo, name); } @@ -116,14 +116,14 @@ } }; - this.showPackageInfoForIndex = function(entryIndex) { + this.showPackageInfoForIndex = function(entryIndex, newTab) { // check whether specified entry index is valid var entry = this.entryByIndex(entryIndex); if(entry) { // show properties var setProperties = function() { // -> find info container and make info table - var infoContainer = document.getElementById("single_package_info_container"); + var infoContainer = repoindex.pageManager.createPackageInfoPane(entry, newTab); infoContainer.wipeChildren(); var infoTable = repoindex.makeInfoTable(); var tb = infoTable.tbodyElement; @@ -202,10 +202,7 @@ } } - // -> make info panel - var infoPanel = repoindex.makeInfoPanel("Package info", repoindex.bind(repoindex.pageManager, repoindex.pageManager.hidePackageInfo)); - infoPanel.bodyElement.appendChild(infoTable.tableElement); - infoContainer.appendChild(infoPanel.element); + infoContainer.appendChild(infoTable.tableElement); }; setProperties(); if(!entry.info.basics || !entry.info.details) { diff --git a/web/js/pagemanagement.js b/web/js/pagemanagement.js index 946bc79..8837b82 100644 --- a/web/js/pagemanagement.js +++ b/web/js/pagemanagement.js @@ -33,7 +33,8 @@ this.rightColumnContainerPackages = document.getElementById("right_column_container_packages"); this.leftColumnContainerRepos = document.getElementById("left_column_container_repos"); this.rightColumnContainerRepos = document.getElementById("right_column_container_repos"); - this.packageInfoContainer = document.getElementById("container_package_info"); + this.packageInfoTabbing = undefined; + this.repoInfoTabbing = undefined; // provide a function to add an error message (shown in red box) this.addError = function(msg) { @@ -269,6 +270,20 @@ return false; }; + this.createPackageInfoPane = function(entry, newTab) { + if(!this.packageInfoTabbing) { + this.packageInfoTabbing = new repoindex.Tabbing(this.rightColumnContainerPackages); + this.packageInfoTabbing.tabRemoved = function(tab) { + if(this.tabs.length === 0) { + repoindex.pageManager.hidePackageInfo(); + } + }; + } + var tabElement = this.packageInfoTabbing.addTab(repoindex.makeElementId([entry.info.repo, entry.name].join("--sep--")), entry.name, true, newTab !== true); + tabElement.activate(); + return tabElement.paneElement; + }; + this.hidePackageInfo = function() { this.rehash(function(hash) { hash[1].repo = hash[1].pkg = undefined; @@ -292,6 +307,20 @@ //} }; + this.createRepoInfoPane = function(entry, newTab) { + if(!this.repoInfoTabbing) { + this.repoInfoTabbing = new repoindex.Tabbing(this.rightColumnContainerRepos); + this.repoInfoTabbing.tabRemoved = function(tab) { + if(this.tabs.length === 0) { + repoindex.pageManager.hideRepoInfo(); + } + }; + } + var tabElement = this.repoInfoTabbing.addTab(repoindex.makeElementId(entry.name), entry.name, true, newTab !== true); + tabElement.activate(); + return tabElement.paneElement; + }; + this.hideRepoInfo = function() { this.rehash(function(hash) { hash[1].repo = hash[1].pkg = undefined; diff --git a/web/js/proto.js b/web/js/proto.js index 08dab90..0a69ae4 100644 --- a/web/js/proto.js +++ b/web/js/proto.js @@ -3,14 +3,14 @@ // let's add some "evil" prototype definitions if(!String.prototype.contains) { - String.prototype.contains = function(startIndex) { - return this.indexOf(startIndex) !== -1; + String.prototype.contains = function(substr) { + return this.indexOf(substr) !== -1; }; } if(!Array.prototype.contains) { - Array.prototype.contains = function(startIndex) { - return this.indexOf(startIndex) !== -1; + Array.prototype.contains = function(element) { + return this.indexOf(element) !== -1; }; } @@ -26,6 +26,12 @@ }; } + if(!Array.prototype.second) { + Array.prototype.second = function() { + return this[1]; + }; + } + if(!Node.prototype.wipeChildren) { Node.prototype.wipeChildren = function() { while(this.lastChild) { diff --git a/web/js/repomanagement.js b/web/js/repomanagement.js index ec88577..0f8de26 100644 --- a/web/js/repomanagement.js +++ b/web/js/repomanagement.js @@ -12,8 +12,8 @@ this.info.currentServer = 0; - this.rowElement.onclick = function() { - repoindex.pageManager.repoManager.showRepoInfoForIndex(this.entry.index); + this.rowElement.onclick = function(e) { + repoindex.pageManager.repoManager.showRepoInfoForIndex(this.entry.index, typeof e === "object" && e.button === 1); }; this.initTableRow = function() { @@ -153,7 +153,7 @@ this.infoBox = document.getElementById("repos_info"); - this.showRepoInfo = function(repo) { + this.showRepoInfo = function(repo, newTab) { var determineEntry = function() { var res = repoindex.pageManager.repoManager.entries.filter(function(entry) { return entry.name === repo; @@ -161,7 +161,7 @@ // entry exists? if(res.length > 0) { // yes -> full package info available? - repoindex.pageManager.repoManager.showRepoInfoForIndex(res[0].index); + repoindex.pageManager.repoManager.showRepoInfoForIndex(res[0].index, newTab); } else { // no -> show error repoindex.pageManager.repoManager.showRepoNotFound(repo); @@ -177,11 +177,11 @@ } }; - this.showRepoInfoForIndex = function(entryIndex) { + this.showRepoInfoForIndex = function(entryIndex, newTab) { var entry = this.entryByIndex(entryIndex); if(entry) { // -> find info container and make info table - var infoContainer = document.getElementById("single_repo_info_container"); + var infoContainer = repoindex.pageManager.createRepoInfoPane(entry, newTab); infoContainer.wipeChildren(); var infoTable = repoindex.makeInfoTable(); var tb = infoTable.tbodyElement; @@ -204,10 +204,7 @@ // ensures, that the "Package Info" box (with the properties just set) is shown repoindex.pageManager.showRepoInfo(); - // -> make info panel - var infoPanel = repoindex.makeInfoPanel("Repository info", repoindex.bind(repoindex.pageManager, repoindex.pageManager.hideRepoInfo)); - infoPanel.bodyElement.appendChild(infoTable.tableElement); - infoContainer.appendChild(infoPanel.element); + infoContainer.appendChild(infoTable.tableElement); } }; diff --git a/web/js/tabbing.js b/web/js/tabbing.js new file mode 100644 index 0000000..a9db54b --- /dev/null +++ b/web/js/tabbing.js @@ -0,0 +1,132 @@ +var repoindex = (function(repoindex) { + + /*! + * \brief Adds bootstrap tabs to the HTML element with the specified id. + */ + repoindex.Tabbing = function(containerId) { + // assemble required element structure + this.containerElement = document.createElement("div"); + this.tabbingElement = document.createElement("ul"); + this.tabbingElement.className = "nav nav-tabs"; + this.contentElement = document.createElement("div"); + this.contentElement.className = "tab-content"; + this.containerElement.appendChild(this.tabbingElement); + this.containerElement.appendChild(this.contentElement); + repoindex.getElement(containerId).appendChild(this.containerElement); + + this.tabs = []; + + // returns a tab by ID + this.tabById = function(id) { + for(var i = 0, len = this.tabs.length; i < len; ++i) { + if(this.tabs[i].paneElement.id === id) { + return this.tabs[i]; + } + } + }; + + // finds the currently shown tab + this.findActiveTab = function() { + for(var i = 0, len = this.tabs.length; i < len; ++i) { + if(this.tabs[i].classList.contains("active")) { + return this.tabs[i]; + } + } + }; + + // adds a new tab + this.addTab = function(id, title, closeable, replaceActive) { + // check whether a tab with the specified ID already exists + var tabElement = this.tabById(id); + if(tabElement) { + // ID already used -> just return currenlty present tab + return tabElement; + } + + // create new tab + tabElement = document.createElement("li"); + + // replace active tab + if(replaceActive) { + var activeTabElement = this.findActiveTab(); + if(activeTabElement) { + this.tabbingElement.removeChild(activeTabElement); + this.contentElement.removeChild(activeTabElement.paneElement); + tabElement.tabbingIndex = activeTabElement.tabbingIndex; + } + } + + // initiate new tab + if(tabElement.tabbingIndex === undefined) { + tabElement.tabbingIndex = this.tabs.length; + } + var paneElement = document.createElement("div"); + var linkElement = document.createElement("a"); + linkElement.setAttribute("data-toggle", "tab"); + linkElement.href = "#" + id; + linkElement.appendChild(document.createTextNode(title)); + tabElement.paneElement = paneElement; + tabElement.linkElement = linkElement; + tabElement.tabbing = this; + tabElement.appendChild(linkElement); + this.tabbingElement.appendChild(tabElement); + paneElement.tabElement = tabElement; + paneElement.setAttribute("id", id); + if(this.tabs.length === 0) { + tabElement.className = "active"; + paneElement.className = "tab-pane fade in active"; + } else { + paneElement.className = "tab-pane fade"; + } + + this.contentElement.appendChild(paneElement); + + // method to active tab + tabElement.activate = function() { + this.linkElement.click(); + }; + + if(closeable) { + var closeButton = repoindex.makeCloseButton(); + closeButton.style.marginLeft = "4px"; + closeButton.onclick = function() { + tabElement.tabbing.removeTab(tabElement); + }; + linkElement.appendChild(closeButton); + }; + + this.tabs[tabElement.tabbingIndex] = tabElement; + return tabElement; + }; + + // removes a tab + this.removeTab = function(tabElement) { + this.tabbingElement.removeChild(tabElement); + this.contentElement.removeChild(tabElement.paneElement); + this.tabs.splice(tabElement.tabbingIndex, 1); + if(this.tabs.length !== 0) { + if(tabElement.tabbingIndex === 0) { + this.tabs[0].activate(); + } else if(tabElement.tabbingIndex - 1 < this.tabs.length) { + this.tabs[tabElement.tabbingIndex - 1].activate(); + } else { + this.tabs[this.tabs.length - 1].activate(); + } + for(var i = tabElement.tabbingIndex, len = this.tabs.length; i < len; ++i) { + this.tabs[i].tabbingIndex = i; + } + } + this.tabRemoved(tabElement); + }; + + // disassembles element strucutre + this.remove = function() { + this.containerElement.parentNode.removeChild(this.containerElement); + }; + + this.tabRemoved = function(tab) {}; + }; + + return repoindex; + +})(repoindex || {}); diff --git a/web/js/utils.js b/web/js/utils.js index 0171ea1..8b1a08f 100644 --- a/web/js/utils.js +++ b/web/js/utils.js @@ -45,6 +45,10 @@ .replace(/'/g, "'"); }; + repoindex.getElement = function(id) { + return typeof id === "string" ? document.getElementById(id) : id; + }; + repoindex.pkgNamesFromDeps = function(dependencyInfos) { if(dependencyInfos) { var names = new Array(dependencyInfos.length); @@ -179,7 +183,7 @@ }; repoindex.setTree = function(id, nodes) { - var element = typeof id === "string" ? document.getElementById(id) : id; + var element = repoindex.getElement(id); element.wipeChildren(); element.className = "file-tree"; // has root? @@ -283,6 +287,21 @@ }); }; + repoindex.makeElementId = function(someName) { + if(someName) { + return "id_" + someName.replace(/\+/g, "--plus--") + .replace(/\?/g, "--question--") + .replace(/\\/g, "--bslash--") + .replace(/\//g, "--slash--") + .replace(/[\[\]\{\}\(\)]/g, "--bracket--") + .replace(/\./g, "--point--") + .replace(/\*/g, "--star--") + .replace(/[^0-1,a-z,A-Z,]/g, "--other--"); + } else { + return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5); + } + }; + repoindex.addField = function(element, fieldName, value) { var nameElement = document.createElement("th"); nameElement.appendChild(document.createTextNode(fieldName)); @@ -301,16 +320,25 @@ repoindex.setPackageNames(repoindex.addField(element, fieldName), packageNames, pageName); }; + repoindex.makeCloseButton = function() { + var closeButton = document.createElement("button"); + closeButton.setAttribute("type", "button"); + closeButton.setAttribute("class", "close"); + closeButton.setAttribute("aria-label", "Close"); + var closeButtonSpan = document.createElement("span"); + closeButtonSpan.setAttribute("aria-hidden", "true"); + closeButtonSpan.appendChild(document.createTextNode("\u00D7")); + closeButton.appendChild(closeButtonSpan); + return closeButton; + }; + repoindex.makeInfoPanel = function(headingText, closeFunc) { var panelElement = document.createElement("div"); panelElement.className = "panel panel-default info-container"; var panelHeadingElement = document.createElement("div"); panelHeadingElement.className = "panel-heading"; panelHeadingElement.appendChild(document.createTextNode(headingText)); - var closeButton = document.createElement("button"); - closeButton.setAttribute("type", "button"); - closeButton.setAttribute("class", "close"); - closeButton.setAttribute("aria-label", "Close"); + var closeButton = repoindex.makeCloseButton(); closeButton.onclick = function() { if(panelElement.parentNode) { panelElement.parentNode.removeChild(panelElement); @@ -319,10 +347,6 @@ } } }; - var closeButtonSpan = document.createElement("span"); - closeButtonSpan.setAttribute("aria-hidden", "true"); - closeButtonSpan.appendChild(document.createTextNode("\u00D7")); - closeButton.appendChild(closeButtonSpan); panelHeadingElement.appendChild(closeButton); panelElement.appendChild(panelHeadingElement); var panelBodyElement = document.createElement("div"); @@ -347,6 +371,7 @@ repoindex.makeInfoTable = function() { var tableElement = document.createElement("table"); + tableElement.className = "info-table"; tableElement.appendChild(repoindex.makeColumnGroup(["30%", "70%"])); var tbodyElement = document.createElement("tbody"); tableElement.appendChild(tbodyElement);