nav tabs on admin dashboard

This commit is contained in:
2019-03-07 00:20:34 -06:00
parent f73d6ae228
commit e4f473f376
11661 changed files with 216240 additions and 1544253 deletions

View File

@@ -1,42 +1,46 @@
"use strict";
module.exports = class Queue {
/**
* @template T
*/
class Queue {
/**
* @param {Iterable<T>=} items The initial elements.
*/
constructor(items) {
this.first = null;
this.last = null;
this.length = 0;
if(items) {
for(const item of items) {
this.enqueue(item);
}
}
/** @private @type {Set<T>} */
this.set = new Set(items);
/** @private @type {Iterator<T>} */
this.iterator = this.set[Symbol.iterator]();
}
/**
* Returns the number of elements in this queue.
* @returns {number} The number of elements in this queue.
*/
get length() {
return this.set.size;
}
/**
* Appends the specified element to this queue.
* @param {T} item The element to add.
* @returns {void}
*/
enqueue(item) {
const first = this.first;
const node = {
item,
next: null
};
if(first === null) {
this.last = node;
} else {
first.next = node;
}
this.first = node;
this.length++;
this.set.add(item);
}
/**
* Retrieves and removes the head of this queue.
* @returns {T | undefined} The head of the queue of `undefined` if this queue is empty.
*/
dequeue() {
const last = this.last;
if(last === null)
return undefined;
const next = last.next;
if(next === null) {
this.first = null;
}
this.last = next;
this.length--;
return last.item;
const result = this.iterator.next();
if (result.done) return undefined;
this.set.delete(result.value);
return result.value;
}
};
}
module.exports = Queue;

View File

@@ -5,13 +5,26 @@
"use strict";
class Semaphore {
/**
* Creates an instance of Semaphore.
*
* @param {number} available the amount available number of "tasks"
* in the Semaphore
*/
constructor(available) {
this.available = available;
/** @type {(function(): void)[]} */
this.waiters = [];
/** @private */
this._continue = this._continue.bind(this);
}
/**
* @param {function(): void} callback function block to capture and run
* @returns {void}
*/
acquire(callback) {
if(this.available > 0) {
if (this.available > 0) {
this.available--;
callback();
} else {
@@ -20,11 +33,19 @@ class Semaphore {
}
release() {
if(this.waiters.length > 0) {
const callback = this.waiters.pop();
process.nextTick(callback);
} else {
this.available++;
this.available++;
if (this.waiters.length > 0) {
process.nextTick(this._continue);
}
}
_continue() {
if (this.available > 0) {
if (this.waiters.length > 0) {
this.available--;
const callback = this.waiters.pop();
callback();
}
}
}
}

View File

@@ -1,45 +1,140 @@
"use strict";
module.exports = class SortableSet extends Set {
/**
* A subset of Set that offers sorting functionality
* @template T item type in set
* @extends {Set<T>}
*/
class SortableSet extends Set {
/**
* Create a new sortable set
* @param {Iterable<T>=} initialIterable The initial iterable value
* @typedef {function(T, T): number} SortFunction
* @param {SortFunction=} defaultSort Default sorting function
*/
constructor(initialIterable, defaultSort) {
super(initialIterable);
/** @private @type {function(T, T): number}} */
this._sortFn = defaultSort;
/** @private @type {function(T, T): number} | null} */
this._lastActiveSortFn = null;
/** @private @type {Map<Function, T[]> | undefined} */
this._cache = undefined;
/** @private @type {Map<Function, T[]|string|number> | undefined} */
this._cacheOrderIndependent = undefined;
}
/**
* @param {any} value - value to add to set
* @returns {SortableSet} - returns itself
* @param {T} value value to add to set
* @returns {this} returns itself
*/
add(value) {
this._lastActiveSortFn = null;
this._invalidateCache();
this._invalidateOrderedCache();
super.add(value);
return this;
}
/**
* @param {Function} sortFn - function to sort the set
* @param {T} value value to delete
* @returns {boolean} true if value existed in set, false otherwise
*/
delete(value) {
this._invalidateCache();
this._invalidateOrderedCache();
return super.delete(value);
}
/**
* @returns {void}
*/
clear() {
this._invalidateCache();
this._invalidateOrderedCache();
return super.clear();
}
/**
* Sort with a comparer function
* @param {SortFunction} sortFn Sorting comparer function
* @returns {void}
*/
sortWith(sortFn) {
if(this.size === 0 || sortFn === this._lastActiveSortFn) {
if (this.size <= 1 || sortFn === this._lastActiveSortFn) {
// already sorted - nothing to do
return;
}
const sortedArray = Array.from(this).sort(sortFn);
super.clear();
for(let i = 0; i < sortedArray.length; i += 1) {
this.add(sortedArray[i]);
for (let i = 0; i < sortedArray.length; i += 1) {
super.add(sortedArray[i]);
}
this._lastActiveSortFn = sortFn;
this._invalidateCache();
}
/**
* @returns {void}
*/
sort() {
this.sortWith(this._sortFn);
}
};
/**
* Get data from cache
* @param {function(SortableSet<T>): T[]} fn function to calculate value
* @returns {T[]} returns result of fn(this), cached until set changes
*/
getFromCache(fn) {
if (this._cache === undefined) {
this._cache = new Map();
} else {
const data = this._cache.get(fn);
if (data !== undefined) {
return data;
}
}
const newData = fn(this);
this._cache.set(fn, newData);
return newData;
}
/**
* @param {function(SortableSet<T>): string|number|T[]} fn function to calculate value
* @returns {any} returns result of fn(this), cached until set changes
*/
getFromUnorderedCache(fn) {
if (this._cacheOrderIndependent === undefined) {
this._cacheOrderIndependent = new Map();
} else {
const data = this._cacheOrderIndependent.get(fn);
if (data !== undefined) {
return data;
}
}
const newData = fn(this);
this._cacheOrderIndependent.set(fn, newData);
return newData;
}
/**
* @private
* @returns {void}
*/
_invalidateCache() {
if (this._cache !== undefined) {
this._cache.clear();
}
}
/**
* @private
* @returns {void}
*/
_invalidateOrderedCache() {
if (this._cacheOrderIndependent !== undefined) {
this._cacheOrderIndependent.clear();
}
}
}
module.exports = SortableSet;

View File

@@ -1,34 +1,71 @@
"use strict";
const path = require("path");
const looksLikeAbsolutePath = (maybeAbsolutePath) => {
/**
* @typedef {Object} MakeRelativePathsCache
* @property {Map<string, Map<string, string>>=} relativePaths
*/
/**
*
* @param {string} maybeAbsolutePath path to check
* @returns {boolean} returns true if path is "Absolute Path"-like
*/
const looksLikeAbsolutePath = maybeAbsolutePath => {
if (/^\/.*\/$/.test(maybeAbsolutePath)) {
// this 'path' is actually a regexp generated by dynamic requires.
// Don't treat it as an absolute path.
return false;
}
return /^(?:[a-z]:\\|\/)/i.test(maybeAbsolutePath);
};
const normalizePathSeparator = (p) => p.replace(/\\/g, "/");
/**
*
* @param {string} p path to normalize
* @returns {string} normalized version of path
*/
const normalizePathSeparator = p => p.replace(/\\/g, "/");
/**
*
* @param {string} context context for relative path
* @param {string} identifier identifier for path
* @returns {string} a converted relative path
*/
const _makePathsRelative = (context, identifier) => {
return identifier
.split(/([|! ])/)
.map(str => looksLikeAbsolutePath(str) ?
normalizePathSeparator(path.relative(context, str)) : str)
.map(str =>
looksLikeAbsolutePath(str)
? normalizePathSeparator(path.relative(context, str))
: str
)
.join("");
};
/**
*
* @param {string} context context used to create relative path
* @param {string} identifier identifier used to create relative path
* @param {MakeRelativePathsCache=} cache the cache object being set
* @returns {string} the returned relative path
*/
exports.makePathsRelative = (context, identifier, cache) => {
if(!cache) return _makePathsRelative(context, identifier);
if (!cache) return _makePathsRelative(context, identifier);
const relativePaths = cache.relativePaths || (cache.relativePaths = new Map());
const relativePaths =
cache.relativePaths || (cache.relativePaths = new Map());
let cachedResult;
let contextCache = relativePaths.get(context);
if(typeof contextCache === "undefined") {
relativePaths.set(context, contextCache = new Map());
if (contextCache === undefined) {
relativePaths.set(context, (contextCache = new Map()));
} else {
cachedResult = contextCache.get(identifier);
}
if(typeof cachedResult !== "undefined") {
if (cachedResult !== undefined) {
return cachedResult;
} else {
const relativePath = _makePathsRelative(context, identifier);
@@ -36,3 +73,30 @@ exports.makePathsRelative = (context, identifier, cache) => {
return relativePath;
}
};
/**
* @param {string} context absolute context path
* @param {string} request any request string may containing absolute paths, query string, etc.
* @returns {string} a new request string avoiding absolute paths when possible
*/
exports.contextify = (context, request) => {
return request
.split("!")
.map(r => {
const splitPath = r.split("?", 2);
if (/^[a-zA-Z]:\\/.test(splitPath[0])) {
splitPath[0] = path.win32.relative(context, splitPath[0]);
if (!/^[a-zA-Z]:\\/.test(splitPath[0])) {
splitPath[0] = splitPath[0].replace(/\\/g, "/");
}
}
if (/^\//.test(splitPath[0])) {
splitPath[0] = path.posix.relative(context, splitPath[0]);
}
if (!/^(\.\.\/|\/|[a-zA-Z]:\\)/.test(splitPath[0])) {
splitPath[0] = "./" + splitPath[0];
}
return splitPath.join("?");
})
.join("!");
};