#931 Updating code to align witn new code standard

This commit is contained in:
knsv
2019-09-12 12:57:36 -07:00
parent a2e3f3d900
commit cf05a8d8fa
4 changed files with 746 additions and 686 deletions

View File

@@ -1,217 +1,214 @@
import moment from 'moment-mini' import moment from 'moment-mini';
import { sanitizeUrl } from '@braintree/sanitize-url' import { sanitizeUrl } from '@braintree/sanitize-url';
import { logger } from '../../logger' import { logger } from '../../logger';
import { getConfig } from '../../config' import { getConfig } from '../../config';
const config = getConfig() const config = getConfig();
let dateFormat = '' let dateFormat = '';
let axisFormat = '' let axisFormat = '';
let excludes = [] let excludes = [];
let title = '' let title = '';
let sections = [] let sections = [];
let tasks = [] let tasks = [];
let currentSection = '' let currentSection = '';
const tags = ['active', 'done', 'crit', 'milestone'] const tags = ['active', 'done', 'crit', 'milestone'];
let funs = [] let funs = [];
let inclusiveEndDates = false let inclusiveEndDates = false;
export const clear = function () { export const clear = function() {
sections = [] sections = [];
tasks = [] tasks = [];
currentSection = '' currentSection = '';
funs = [] funs = [];
title = '' title = '';
taskCnt = 0 taskCnt = 0;
lastTask = undefined lastTask = undefined;
lastTaskID = undefined lastTaskID = undefined;
rawTasks = [] rawTasks = [];
dateFormat = '' dateFormat = '';
axisFormat = '' axisFormat = '';
excludes = [] excludes = [];
inclusiveEndDates = false inclusiveEndDates = false;
} };
export const setAxisFormat = function (txt) { export const setAxisFormat = function(txt) {
axisFormat = txt axisFormat = txt;
} };
export const getAxisFormat = function () { export const getAxisFormat = function() {
return axisFormat return axisFormat;
} };
export const setDateFormat = function (txt) { export const setDateFormat = function(txt) {
dateFormat = txt dateFormat = txt;
} };
export const enableInclusiveEndDates = function () { export const enableInclusiveEndDates = function() {
inclusiveEndDates = true inclusiveEndDates = true;
} };
export const endDatesAreInclusive = function () { export const endDatesAreInclusive = function() {
return inclusiveEndDates return inclusiveEndDates;
} };
export const getDateFormat = function () { export const getDateFormat = function() {
return dateFormat return dateFormat;
} };
export const setExcludes = function (txt) { export const setExcludes = function(txt) {
excludes = txt.toLowerCase().split(/[\s,]+/) excludes = txt.toLowerCase().split(/[\s,]+/);
} };
export const getExcludes = function () { export const getExcludes = function() {
return excludes return excludes;
} };
export const setTitle = function (txt) { export const setTitle = function(txt) {
title = txt title = txt;
} };
export const getTitle = function () { export const getTitle = function() {
return title return title;
} };
export const addSection = function (txt) { export const addSection = function(txt) {
currentSection = txt currentSection = txt;
sections.push(txt) sections.push(txt);
} };
export const getSections = function () { export const getSections = function() {
return sections return sections;
} };
export const getTasks = function () { export const getTasks = function() {
let allItemsPricessed = compileTasks() let allItemsPricessed = compileTasks();
const maxDepth = 10 const maxDepth = 10;
let iterationCount = 0 let iterationCount = 0;
while (!allItemsPricessed && (iterationCount < maxDepth)) { while (!allItemsPricessed && iterationCount < maxDepth) {
allItemsPricessed = compileTasks() allItemsPricessed = compileTasks();
iterationCount++ iterationCount++;
} }
tasks = rawTasks tasks = rawTasks;
return tasks return tasks;
} };
const isInvalidDate = function (date, dateFormat, excludes) { const isInvalidDate = function(date, dateFormat, excludes) {
if (date.isoWeekday() >= 6 && excludes.indexOf('weekends') >= 0) { if (date.isoWeekday() >= 6 && excludes.indexOf('weekends') >= 0) {
return true return true;
} }
if (excludes.indexOf(date.format('dddd').toLowerCase()) >= 0) { if (excludes.indexOf(date.format('dddd').toLowerCase()) >= 0) {
return true return true;
} }
return excludes.indexOf(date.format(dateFormat.trim())) >= 0 return excludes.indexOf(date.format(dateFormat.trim())) >= 0;
} };
const checkTaskDates = function (task, dateFormat, excludes) { const checkTaskDates = function(task, dateFormat, excludes) {
if (!excludes.length || task.manualEndTime) return if (!excludes.length || task.manualEndTime) return;
let startTime = moment(task.startTime, dateFormat, true) let startTime = moment(task.startTime, dateFormat, true);
startTime.add(1, 'd') startTime.add(1, 'd');
let endTime = moment(task.endTime, dateFormat, true) let endTime = moment(task.endTime, dateFormat, true);
let renderEndTime = fixTaskDates(startTime, endTime, dateFormat, excludes) let renderEndTime = fixTaskDates(startTime, endTime, dateFormat, excludes);
task.endTime = endTime.toDate() task.endTime = endTime.toDate();
task.renderEndTime = renderEndTime task.renderEndTime = renderEndTime;
} };
const fixTaskDates = function (startTime, endTime, dateFormat, excludes) { const fixTaskDates = function(startTime, endTime, dateFormat, excludes) {
let invalid = false let invalid = false;
let renderEndTime = null let renderEndTime = null;
while (startTime.date() <= endTime.date()) { while (startTime.date() <= endTime.date()) {
if (!invalid) { if (!invalid) {
renderEndTime = endTime.toDate() renderEndTime = endTime.toDate();
} }
invalid = isInvalidDate(startTime, dateFormat, excludes) invalid = isInvalidDate(startTime, dateFormat, excludes);
if (invalid) { if (invalid) {
endTime.add(1, 'd') endTime.add(1, 'd');
} }
startTime.add(1, 'd') startTime.add(1, 'd');
} }
return renderEndTime return renderEndTime;
} };
const getStartDate = function (prevTime, dateFormat, str) { const getStartDate = function(prevTime, dateFormat, str) {
str = str.trim() str = str.trim();
// Test for after // Test for after
const re = /^after\s+([\d\w-]+)/ const re = /^after\s+([\d\w-]+)/;
const afterStatement = re.exec(str.trim()) const afterStatement = re.exec(str.trim());
if (afterStatement !== null) { if (afterStatement !== null) {
const task = findTaskById(afterStatement[1]) const task = findTaskById(afterStatement[1]);
if (typeof task === 'undefined') { if (typeof task === 'undefined') {
const dt = new Date() const dt = new Date();
dt.setHours(0, 0, 0, 0) dt.setHours(0, 0, 0, 0);
return dt return dt;
} }
return task.endTime return task.endTime;
} }
// Check for actual date set // Check for actual date set
let mDate = moment(str, dateFormat.trim(), true) let mDate = moment(str, dateFormat.trim(), true);
if (mDate.isValid()) { if (mDate.isValid()) {
return mDate.toDate() return mDate.toDate();
} else { } else {
logger.debug('Invalid date:' + str) logger.debug('Invalid date:' + str);
logger.debug('With date format:' + dateFormat.trim()) logger.debug('With date format:' + dateFormat.trim());
} }
// Default date - now // Default date - now
return new Date() return new Date();
} };
const durationToDate = function (durationStatement, relativeTime) { const durationToDate = function(durationStatement, relativeTime) {
if (durationStatement !== null) { if (durationStatement !== null) {
switch (durationStatement[2]) { switch (durationStatement[2]) {
case 's': case 's':
relativeTime.add(durationStatement[1], 'seconds') relativeTime.add(durationStatement[1], 'seconds');
break break;
case 'm': case 'm':
relativeTime.add(durationStatement[1], 'minutes') relativeTime.add(durationStatement[1], 'minutes');
break break;
case 'h': case 'h':
relativeTime.add(durationStatement[1], 'hours') relativeTime.add(durationStatement[1], 'hours');
break break;
case 'd': case 'd':
relativeTime.add(durationStatement[1], 'days') relativeTime.add(durationStatement[1], 'days');
break break;
case 'w': case 'w':
relativeTime.add(durationStatement[1], 'weeks') relativeTime.add(durationStatement[1], 'weeks');
break break;
} }
} }
// Default date - now // Default date - now
return relativeTime.toDate() return relativeTime.toDate();
} };
const getEndDate = function (prevTime, dateFormat, str, inclusive) { const getEndDate = function(prevTime, dateFormat, str, inclusive) {
inclusive = inclusive || false inclusive = inclusive || false;
str = str.trim() str = str.trim();
// Check for actual date // Check for actual date
let mDate = moment(str, dateFormat.trim(), true) let mDate = moment(str, dateFormat.trim(), true);
if (mDate.isValid()) { if (mDate.isValid()) {
if (inclusive) { if (inclusive) {
mDate.add(1, 'd') mDate.add(1, 'd');
} }
return mDate.toDate() return mDate.toDate();
} }
return durationToDate( return durationToDate(/^([\d]+)([wdhms])/.exec(str.trim()), moment(prevTime));
/^([\d]+)([wdhms])/.exec(str.trim()), };
moment(prevTime)
)
}
let taskCnt = 0 let taskCnt = 0;
const parseId = function (idStr) { const parseId = function(idStr) {
if (typeof idStr === 'undefined') { if (typeof idStr === 'undefined') {
taskCnt = taskCnt + 1 taskCnt = taskCnt + 1;
return 'task' + taskCnt return 'task' + taskCnt;
} }
return idStr return idStr;
} };
// id, startDate, endDate // id, startDate, endDate
// id, startDate, length // id, startDate, length
// id, after x, endDate // id, after x, endDate
@@ -223,116 +220,116 @@ const parseId = function (idStr) {
// endDate // endDate
// length // length
const compileData = function (prevTask, dataStr) { const compileData = function(prevTask, dataStr) {
let ds let ds;
if (dataStr.substr(0, 1) === ':') { if (dataStr.substr(0, 1) === ':') {
ds = dataStr.substr(1, dataStr.length) ds = dataStr.substr(1, dataStr.length);
} else { } else {
ds = dataStr ds = dataStr;
} }
const data = ds.split(',') const data = ds.split(',');
const task = {} const task = {};
// Get tags like active, done, crit and milestone // Get tags like active, done, crit and milestone
getTaskTags(data, task, tags) getTaskTags(data, task, tags);
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
data[i] = data[i].trim() data[i] = data[i].trim();
} }
let endTimeData = '' let endTimeData = '';
switch (data.length) { switch (data.length) {
case 1: case 1:
task.id = parseId() task.id = parseId();
task.startTime = prevTask.endTime task.startTime = prevTask.endTime;
endTimeData = data[0] endTimeData = data[0];
break break;
case 2: case 2:
task.id = parseId() task.id = parseId();
task.startTime = getStartDate(undefined, dateFormat, data[0]) task.startTime = getStartDate(undefined, dateFormat, data[0]);
endTimeData = data[1] endTimeData = data[1];
break break;
case 3: case 3:
task.id = parseId(data[0]) task.id = parseId(data[0]);
task.startTime = getStartDate(undefined, dateFormat, data[1]) task.startTime = getStartDate(undefined, dateFormat, data[1]);
endTimeData = data[2] endTimeData = data[2];
break break;
default: default:
} }
if (endTimeData) { if (endTimeData) {
task.endTime = getEndDate(task.startTime, dateFormat, endTimeData, inclusiveEndDates) task.endTime = getEndDate(task.startTime, dateFormat, endTimeData, inclusiveEndDates);
task.manualEndTime = moment(endTimeData, 'YYYY-MM-DD', true).isValid() task.manualEndTime = moment(endTimeData, 'YYYY-MM-DD', true).isValid();
checkTaskDates(task, dateFormat, excludes) checkTaskDates(task, dateFormat, excludes);
} }
return task return task;
} };
const parseData = function (prevTaskId, dataStr) { const parseData = function(prevTaskId, dataStr) {
let ds let ds;
if (dataStr.substr(0, 1) === ':') { if (dataStr.substr(0, 1) === ':') {
ds = dataStr.substr(1, dataStr.length) ds = dataStr.substr(1, dataStr.length);
} else { } else {
ds = dataStr ds = dataStr;
} }
const data = ds.split(',') const data = ds.split(',');
const task = {} const task = {};
// Get tags like active, done, crit and milestone // Get tags like active, done, crit and milestone
getTaskTags(data, task, tags) getTaskTags(data, task, tags);
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
data[i] = data[i].trim() data[i] = data[i].trim();
} }
switch (data.length) { switch (data.length) {
case 1: case 1:
task.id = parseId() task.id = parseId();
task.startTime = { task.startTime = {
type: 'prevTaskEnd', type: 'prevTaskEnd',
id: prevTaskId id: prevTaskId
} };
task.endTime = { task.endTime = {
data: data[0] data: data[0]
} };
break break;
case 2: case 2:
task.id = parseId() task.id = parseId();
task.startTime = { task.startTime = {
type: 'getStartDate', type: 'getStartDate',
startData: data[0] startData: data[0]
} };
task.endTime = { task.endTime = {
data: data[1] data: data[1]
} };
break break;
case 3: case 3:
task.id = parseId(data[0]) task.id = parseId(data[0]);
task.startTime = { task.startTime = {
type: 'getStartDate', type: 'getStartDate',
startData: data[1] startData: data[1]
} };
task.endTime = { task.endTime = {
data: data[2] data: data[2]
} };
break break;
default: default:
} }
return task return task;
} };
let lastTask let lastTask;
let lastTaskID let lastTaskID;
let rawTasks = [] let rawTasks = [];
const taskDb = {} const taskDb = {};
export const addTask = function (descr, data) { export const addTask = function(descr, data) {
const rawTask = { const rawTask = {
section: currentSection, section: currentSection,
type: currentSection, type: currentSection,
@@ -342,174 +339,187 @@ export const addTask = function (descr, data) {
raw: { data: data }, raw: { data: data },
task: descr, task: descr,
classes: [] classes: []
} };
const taskInfo = parseData(lastTaskID, data) const taskInfo = parseData(lastTaskID, data);
rawTask.raw.startTime = taskInfo.startTime rawTask.raw.startTime = taskInfo.startTime;
rawTask.raw.endTime = taskInfo.endTime rawTask.raw.endTime = taskInfo.endTime;
rawTask.id = taskInfo.id rawTask.id = taskInfo.id;
rawTask.prevTaskId = lastTaskID rawTask.prevTaskId = lastTaskID;
rawTask.active = taskInfo.active rawTask.active = taskInfo.active;
rawTask.done = taskInfo.done rawTask.done = taskInfo.done;
rawTask.crit = taskInfo.crit rawTask.crit = taskInfo.crit;
rawTask.milestone = taskInfo.milestone rawTask.milestone = taskInfo.milestone;
const pos = rawTasks.push(rawTask) const pos = rawTasks.push(rawTask);
lastTaskID = rawTask.id lastTaskID = rawTask.id;
// Store cross ref // Store cross ref
taskDb[rawTask.id] = pos - 1 taskDb[rawTask.id] = pos - 1;
} };
export const findTaskById = function (id) { export const findTaskById = function(id) {
const pos = taskDb[id] const pos = taskDb[id];
return rawTasks[pos] return rawTasks[pos];
} };
export const addTaskOrg = function (descr, data) { export const addTaskOrg = function(descr, data) {
const newTask = { const newTask = {
section: currentSection, section: currentSection,
type: currentSection, type: currentSection,
description: descr, description: descr,
task: descr, task: descr,
classes: [] classes: []
} };
const taskInfo = compileData(lastTask, data) const taskInfo = compileData(lastTask, data);
newTask.startTime = taskInfo.startTime newTask.startTime = taskInfo.startTime;
newTask.endTime = taskInfo.endTime newTask.endTime = taskInfo.endTime;
newTask.id = taskInfo.id newTask.id = taskInfo.id;
newTask.active = taskInfo.active newTask.active = taskInfo.active;
newTask.done = taskInfo.done newTask.done = taskInfo.done;
newTask.crit = taskInfo.crit newTask.crit = taskInfo.crit;
newTask.milestone = taskInfo.milestone newTask.milestone = taskInfo.milestone;
lastTask = newTask lastTask = newTask;
tasks.push(newTask) tasks.push(newTask);
} };
const compileTasks = function () { const compileTasks = function() {
const compileTask = function (pos) { const compileTask = function(pos) {
const task = rawTasks[pos] const task = rawTasks[pos];
let startTime = '' let startTime = '';
switch (rawTasks[pos].raw.startTime.type) { switch (rawTasks[pos].raw.startTime.type) {
case 'prevTaskEnd': case 'prevTaskEnd':
const prevTask = findTaskById(task.prevTaskId) const prevTask = findTaskById(task.prevTaskId);
task.startTime = prevTask.endTime task.startTime = prevTask.endTime;
break break;
case 'getStartDate': case 'getStartDate':
startTime = getStartDate(undefined, dateFormat, rawTasks[pos].raw.startTime.startData) startTime = getStartDate(undefined, dateFormat, rawTasks[pos].raw.startTime.startData);
if (startTime) { if (startTime) {
rawTasks[pos].startTime = startTime rawTasks[pos].startTime = startTime;
} }
break break;
} }
if (rawTasks[pos].startTime) { if (rawTasks[pos].startTime) {
rawTasks[pos].endTime = getEndDate(rawTasks[pos].startTime, dateFormat, rawTasks[pos].raw.endTime.data, inclusiveEndDates) rawTasks[pos].endTime = getEndDate(
rawTasks[pos].startTime,
dateFormat,
rawTasks[pos].raw.endTime.data,
inclusiveEndDates
);
if (rawTasks[pos].endTime) { if (rawTasks[pos].endTime) {
rawTasks[pos].processed = true rawTasks[pos].processed = true;
rawTasks[pos].manualEndTime = moment(rawTasks[pos].raw.endTime.data, 'YYYY-MM-DD', true).isValid() rawTasks[pos].manualEndTime = moment(
checkTaskDates(rawTasks[pos], dateFormat, excludes) rawTasks[pos].raw.endTime.data,
'YYYY-MM-DD',
true
).isValid();
checkTaskDates(rawTasks[pos], dateFormat, excludes);
} }
} }
return rawTasks[pos].processed return rawTasks[pos].processed;
} };
let allProcessed = true let allProcessed = true;
for (let i = 0; i < rawTasks.length; i++) { for (let i = 0; i < rawTasks.length; i++) {
compileTask(i) compileTask(i);
allProcessed = allProcessed && rawTasks[i].processed allProcessed = allProcessed && rawTasks[i].processed;
} }
return allProcessed return allProcessed;
} };
/** /**
* Called by parser when a link is found. Adds the URL to the vertex data. * Called by parser when a link is found. Adds the URL to the vertex data.
* @param ids Comma separated list of ids * @param ids Comma separated list of ids
* @param linkStr URL to create a link for * @param linkStr URL to create a link for
*/ */
export const setLink = function (ids, _linkStr) { export const setLink = function(ids, _linkStr) {
let linkStr = _linkStr let linkStr = _linkStr;
if (config.securityLevel !== 'loose') { if (config.securityLevel !== 'loose') {
linkStr = sanitizeUrl(_linkStr) linkStr = sanitizeUrl(_linkStr);
} }
ids.split(',').forEach(function (id) { ids.split(',').forEach(function(id) {
let rawTask = findTaskById(id) let rawTask = findTaskById(id);
if (typeof rawTask !== 'undefined') { if (typeof rawTask !== 'undefined') {
pushFun(id, () => { window.open(linkStr, '_self') }) pushFun(id, () => {
window.open(linkStr, '_self');
});
} }
}) });
setClass(ids, 'clickable') setClass(ids, 'clickable');
} };
/** /**
* Called by parser when a special node is found, e.g. a clickable element. * Called by parser when a special node is found, e.g. a clickable element.
* @param ids Comma separated list of ids * @param ids Comma separated list of ids
* @param className Class to add * @param className Class to add
*/ */
export const setClass = function (ids, className) { export const setClass = function(ids, className) {
ids.split(',').forEach(function (id) { ids.split(',').forEach(function(id) {
let rawTask = findTaskById(id) let rawTask = findTaskById(id);
if (typeof rawTask !== 'undefined') { if (typeof rawTask !== 'undefined') {
rawTask.classes.push(className) rawTask.classes.push(className);
} }
}) });
} };
const setClickFun = function (id, functionName, functionArgs) { const setClickFun = function(id, functionName, functionArgs) {
if (config.securityLevel !== 'loose') { if (config.securityLevel !== 'loose') {
return return;
} }
if (typeof functionName === 'undefined') { if (typeof functionName === 'undefined') {
return return;
} }
let argList = [] let argList = [];
if (typeof functionArgs === 'string') { if (typeof functionArgs === 'string') {
/* Splits functionArgs by ',', ignoring all ',' in double quoted strings */ /* Splits functionArgs by ',', ignoring all ',' in double quoted strings */
argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/) argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
for (let i = 0; i < argList.length; i++) { for (let i = 0; i < argList.length; i++) {
let item = argList[i].trim() let item = argList[i].trim();
/* Removes all double quotes at the start and end of an argument */ /* Removes all double quotes at the start and end of an argument */
/* This preserves all starting and ending whitespace inside */ /* This preserves all starting and ending whitespace inside */
if (item.charAt(0) === '"' && item.charAt(item.length - 1) === '"') { if (item.charAt(0) === '"' && item.charAt(item.length - 1) === '"') {
item = item.substr(1, item.length - 2) item = item.substr(1, item.length - 2);
} }
argList[i] = item argList[i] = item;
} }
} }
let rawTask = findTaskById(id) let rawTask = findTaskById(id);
if (typeof rawTask !== 'undefined') { if (typeof rawTask !== 'undefined') {
pushFun(id, () => { window[functionName](...argList) }) pushFun(id, () => {
window[functionName](...argList);
});
} }
} };
/** /**
* The callbackFunction is executed in a click event bound to the task with the specified id or the task's assigned text * The callbackFunction is executed in a click event bound to the task with the specified id or the task's assigned text
* @param id The task's id * @param id The task's id
* @param callbackFunction A function to be executed when clicked on the task or the task's text * @param callbackFunction A function to be executed when clicked on the task or the task's text
*/ */
const pushFun = function (id, callbackFunction) { const pushFun = function(id, callbackFunction) {
funs.push(function (element) { funs.push(function(element) {
// const elem = d3.select(element).select(`[id="${id}"]`) // const elem = d3.select(element).select(`[id="${id}"]`)
const elem = document.querySelector(`[id="${id}"]`) const elem = document.querySelector(`[id="${id}"]`);
if (elem !== null) { if (elem !== null) {
elem.addEventListener('click', function () { elem.addEventListener('click', function() {
callbackFunction() callbackFunction();
}) });
} }
}) });
funs.push(function (element) { funs.push(function(element) {
// const elem = d3.select(element).select(`[id="${id}-text"]`) // const elem = d3.select(element).select(`[id="${id}-text"]`)
const elem = document.querySelector(`[id="${id}-text"]`) const elem = document.querySelector(`[id="${id}-text"]`);
if (elem !== null) { if (elem !== null) {
elem.addEventListener('click', function () { elem.addEventListener('click', function() {
callbackFunction() callbackFunction();
}) });
} }
}) });
} };
/** /**
* Called by parser when a click definition is found. Registers an event handler. * Called by parser when a click definition is found. Registers an event handler.
@@ -517,22 +527,22 @@ const pushFun = function (id, callbackFunction) {
* @param functionName Function to be called on click * @param functionName Function to be called on click
* @param functionArgs Function args the function should be called with * @param functionArgs Function args the function should be called with
*/ */
export const setClickEvent = function (ids, functionName, functionArgs) { export const setClickEvent = function(ids, functionName, functionArgs) {
ids.split(',').forEach(function (id) { ids.split(',').forEach(function(id) {
setClickFun(id, functionName, functionArgs) setClickFun(id, functionName, functionArgs);
}) });
setClass(ids, 'clickable') setClass(ids, 'clickable');
} };
/** /**
* Binds all functions previously added to fun (specified through click) to the element * Binds all functions previously added to fun (specified through click) to the element
* @param element * @param element
*/ */
export const bindFunctions = function (element) { export const bindFunctions = function(element) {
funs.forEach(function (fun) { funs.forEach(function(fun) {
fun(element) fun(element);
}) });
} };
export default { export default {
clear, clear,
@@ -556,20 +566,20 @@ export default {
setLink, setLink,
bindFunctions, bindFunctions,
durationToDate durationToDate
} };
function getTaskTags (data, task, tags) { function getTaskTags(data, task, tags) {
let matchFound = true let matchFound = true;
while (matchFound) { while (matchFound) {
matchFound = false matchFound = false;
tags.forEach(function (t) { tags.forEach(function(t) {
const pattern = '^\\s*' + t + '\\s*$' const pattern = '^\\s*' + t + '\\s*$';
const regex = new RegExp(pattern) const regex = new RegExp(pattern);
if (data[0].match(regex)) { if (data[0].match(regex)) {
task[t] = true task[t] = true;
data.shift(1) data.shift(1);
matchFound = true matchFound = true;
} }
}) });
} }
} }

View File

@@ -1,32 +1,32 @@
/* eslint-env jasmine */ /* eslint-env jasmine */
import moment from 'moment-mini' import moment from 'moment-mini';
import ganttDb from './ganttDb' import ganttDb from './ganttDb';
describe('when using the ganttDb', function () { describe('when using the ganttDb', function() {
beforeEach(function () { beforeEach(function() {
ganttDb.clear() ganttDb.clear();
}) });
describe('when using relative times', function () { describe('when using relative times', function() {
it.each` it.each`
diff | date | expected diff | date | expected
${' 1d'} | ${moment('2019-01-01')} | ${moment('2019-01-02').toDate()} ${' 1d'} | ${moment('2019-01-01')} | ${moment('2019-01-02').toDate()}
${' 1w'} | ${moment('2019-01-01')} | ${moment('2019-01-08').toDate()} ${' 1w'} | ${moment('2019-01-01')} | ${moment('2019-01-08').toDate()}
`('should add $diff to $date resulting in $expected', ({ diff, date, expected }) => { `('should add $diff to $date resulting in $expected', ({ diff, date, expected }) => {
expect(ganttDb.durationToDate(diff, date)).toEqual(expected) expect(ganttDb.durationToDate(diff, date)).toEqual(expected);
}) });
}) });
describe('when calling the clear function', function () { describe('when calling the clear function', function() {
beforeEach(function () { beforeEach(function() {
ganttDb.setDateFormat('YYYY-MM-DD') ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.enableInclusiveEndDates() ganttDb.enableInclusiveEndDates();
ganttDb.setExcludes('weekends 2019-02-06,friday') ganttDb.setExcludes('weekends 2019-02-06,friday');
ganttDb.addSection('weekends skip test') ganttDb.addSection('weekends skip test');
ganttDb.addTask('test1', 'id1,2019-02-01,1d') ganttDb.addTask('test1', 'id1,2019-02-01,1d');
ganttDb.addTask('test2', 'id2,after id1,2d') ganttDb.addTask('test2', 'id2,after id1,2d');
ganttDb.clear() ganttDb.clear();
}) });
it.each` it.each`
fn | expected fn | expected
@@ -38,9 +38,9 @@ describe('when using the ganttDb', function () {
${'getSections'} | ${[]} ${'getSections'} | ${[]}
${'endDatesAreInclusive'} | ${false} ${'endDatesAreInclusive'} | ${false}
`('should clear $fn', ({ fn, expected }) => { `('should clear $fn', ({ fn, expected }) => {
expect(ganttDb[ fn ]()).toEqual(expected) expect(ganttDb[fn]()).toEqual(expected);
}) });
}) });
it.each` it.each`
testName | section | taskName | taskData | expStartDate | expEndDate | expId | expTask testName | section | taskName | taskData | expStartDate | expEndDate | expId | expTask
@@ -52,135 +52,148 @@ describe('when using the ganttDb', function () {
${'should handle duration (weeks) instead of fixed date to determine end date'} | ${'testa1'} | ${'test1'} | ${'id1,2013-01-01,2w'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 15)} | ${'id1'} | ${'test1'} ${'should handle duration (weeks) instead of fixed date to determine end date'} | ${'testa1'} | ${'test1'} | ${'id1,2013-01-01,2w'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 15)} | ${'id1'} | ${'test1'}
${'should handle fixed dates without id'} | ${'testa1'} | ${'test1'} | ${'2013-01-01,2013-01-12'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 12)} | ${'task1'} | ${'test1'} ${'should handle fixed dates without id'} | ${'testa1'} | ${'test1'} | ${'2013-01-01,2013-01-12'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 12)} | ${'task1'} | ${'test1'}
${'should handle duration instead of a fixed date to determine end date without id'} | ${'testa1'} | ${'test1'} | ${'2013-01-01,4d'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 5)} | ${'task1'} | ${'test1'} ${'should handle duration instead of a fixed date to determine end date without id'} | ${'testa1'} | ${'test1'} | ${'2013-01-01,4d'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 5)} | ${'task1'} | ${'test1'}
`('$testName', ({ section, taskName, taskData, expStartDate, expEndDate, expId, expTask }) => { `('$testName', ({ section, taskName, taskData, expStartDate, expEndDate, expId, expTask }) => {
ganttDb.setDateFormat('YYYY-MM-DD') ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.addSection(section) ganttDb.addSection(section);
ganttDb.addTask(taskName, taskData) ganttDb.addTask(taskName, taskData);
const tasks = ganttDb.getTasks() const tasks = ganttDb.getTasks();
expect(tasks[0].startTime).toEqual(expStartDate) expect(tasks[0].startTime).toEqual(expStartDate);
expect(tasks[0].endTime).toEqual(expEndDate) expect(tasks[0].endTime).toEqual(expEndDate);
expect(tasks[0].id).toEqual(expId) expect(tasks[0].id).toEqual(expId);
expect(tasks[0].task).toEqual(expTask) expect(tasks[0].task).toEqual(expTask);
}) });
it.each` it.each`
section | taskName1 | taskName2 | taskData1 | taskData2 | expStartDate2 | expEndDate2 | expId2 | expTask2 section | taskName1 | taskName2 | taskData1 | taskData2 | expStartDate2 | expEndDate2 | expId2 | expTask2
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'id2'} | ${'test2'} ${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'id2'} | ${'test2'}
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id3,1d'} | ${new Date((new Date()).setHours(0, 0, 0, 0))} | ${undefined} | ${'id2'} | ${'test2'} ${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id3,1d'} | ${new Date(new Date().setHours(0, 0, 0, 0))} | ${undefined} | ${'id2'} | ${'test2'}
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'task1'} | ${'test2'} ${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'task1'} | ${'test2'}
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'2013-01-26'} | ${new Date(2013, 0, 15)} | ${new Date(2013, 0, 26)} | ${'task1'} | ${'test2'} ${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'2013-01-26'} | ${new Date(2013, 0, 15)} | ${new Date(2013, 0, 26)} | ${'task1'} | ${'test2'}
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'2d'} | ${new Date(2013, 0, 15)} | ${new Date(2013, 0, 17)} | ${'task1'} | ${'test2'} ${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'2d'} | ${new Date(2013, 0, 15)} | ${new Date(2013, 0, 17)} | ${'task1'} | ${'test2'}
`('$testName', ({ section, taskName1, taskName2, taskData1, taskData2, expStartDate2, expEndDate2, expId2, expTask2 }) => { `(
ganttDb.setDateFormat('YYYY-MM-DD') '$testName',
ganttDb.addSection(section) ({
ganttDb.addTask(taskName1, taskData1) section,
ganttDb.addTask(taskName2, taskData2) taskName1,
const tasks = ganttDb.getTasks() taskName2,
expect(tasks[1].startTime).toEqual(expStartDate2) taskData1,
taskData2,
expStartDate2,
expEndDate2,
expId2,
expTask2
}) => {
ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.addSection(section);
ganttDb.addTask(taskName1, taskData1);
ganttDb.addTask(taskName2, taskData2);
const tasks = ganttDb.getTasks();
expect(tasks[1].startTime).toEqual(expStartDate2);
if (!expEndDate2 === undefined) { if (!expEndDate2 === undefined) {
expect(tasks[1].endTime).toEqual(expEndDate2) expect(tasks[1].endTime).toEqual(expEndDate2);
} }
expect(tasks[1].id).toEqual(expId2) expect(tasks[1].id).toEqual(expId2);
expect(tasks[1].task).toEqual(expTask2) expect(tasks[1].task).toEqual(expTask2);
}) }
);
it('should handle relative start date based on id regardless of sections', function () { it('should handle relative start date based on id regardless of sections', function() {
ganttDb.setDateFormat('YYYY-MM-DD') ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.addSection('testa1') ganttDb.addSection('testa1');
ganttDb.addTask('test1', 'id1,2013-01-01,2w') ganttDb.addTask('test1', 'id1,2013-01-01,2w');
ganttDb.addTask('test2', 'id2,after id3,1d') ganttDb.addTask('test2', 'id2,after id3,1d');
ganttDb.addSection('testa2') ganttDb.addSection('testa2');
ganttDb.addTask('test3', 'id3,after id1,2d') ganttDb.addTask('test3', 'id3,after id1,2d');
const tasks = ganttDb.getTasks() const tasks = ganttDb.getTasks();
expect(tasks[1].startTime).toEqual(new Date(2013, 0, 17)) expect(tasks[1].startTime).toEqual(new Date(2013, 0, 17));
expect(tasks[1].endTime).toEqual(new Date(2013, 0, 18)) expect(tasks[1].endTime).toEqual(new Date(2013, 0, 18));
expect(tasks[1].id).toEqual('id2') expect(tasks[1].id).toEqual('id2');
expect(tasks[1].task).toEqual('test2') expect(tasks[1].task).toEqual('test2');
expect(tasks[2].id).toEqual('id3') expect(tasks[2].id).toEqual('id3');
expect(tasks[2].task).toEqual('test3') expect(tasks[2].task).toEqual('test3');
expect(tasks[2].startTime).toEqual(new Date(2013, 0, 15)) expect(tasks[2].startTime).toEqual(new Date(2013, 0, 15));
expect(tasks[2].endTime).toEqual(new Date(2013, 0, 17)) expect(tasks[2].endTime).toEqual(new Date(2013, 0, 17));
}) });
it('should ignore weekends', function () { it('should ignore weekends', function() {
ganttDb.setDateFormat('YYYY-MM-DD') ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.setExcludes('weekends 2019-02-06,friday') ganttDb.setExcludes('weekends 2019-02-06,friday');
ganttDb.addSection('weekends skip test') ganttDb.addSection('weekends skip test');
ganttDb.addTask('test1', 'id1,2019-02-01,1d') ganttDb.addTask('test1', 'id1,2019-02-01,1d');
ganttDb.addTask('test2', 'id2,after id1,2d') ganttDb.addTask('test2', 'id2,after id1,2d');
ganttDb.addTask('test3', 'id3,after id2,7d') ganttDb.addTask('test3', 'id3,after id2,7d');
ganttDb.addTask('test4', 'id4,2019-02-01,2019-02-20') // Fixed endTime ganttDb.addTask('test4', 'id4,2019-02-01,2019-02-20'); // Fixed endTime
ganttDb.addTask('test5', 'id5,after id4,1d') ganttDb.addTask('test5', 'id5,after id4,1d');
ganttDb.addSection('full ending taks on last day') ganttDb.addSection('full ending taks on last day');
ganttDb.addTask('test6', 'id6,2019-02-13,2d') ganttDb.addTask('test6', 'id6,2019-02-13,2d');
ganttDb.addTask('test7', 'id7,after id6,1d') ganttDb.addTask('test7', 'id7,after id6,1d');
const tasks = ganttDb.getTasks() const tasks = ganttDb.getTasks();
expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate()) expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate()) expect(tasks[0].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate());
expect(tasks[0].renderEndTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate()) expect(tasks[0].renderEndTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate());
expect(tasks[0].id).toEqual('id1') expect(tasks[0].id).toEqual('id1');
expect(tasks[0].task).toEqual('test1') expect(tasks[0].task).toEqual('test1');
expect(tasks[1].startTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate()) expect(tasks[1].startTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate()) expect(tasks[1].endTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate());
expect(tasks[1].renderEndTime).toEqual(moment('2019-02-06', 'YYYY-MM-DD').toDate()) expect(tasks[1].renderEndTime).toEqual(moment('2019-02-06', 'YYYY-MM-DD').toDate());
expect(tasks[1].id).toEqual('id2') expect(tasks[1].id).toEqual('id2');
expect(tasks[1].task).toEqual('test2') expect(tasks[1].task).toEqual('test2');
expect(tasks[2].startTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate()) expect(tasks[2].startTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate());
expect(tasks[2].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate()) expect(tasks[2].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[2].renderEndTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate()) expect(tasks[2].renderEndTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[2].id).toEqual('id3') expect(tasks[2].id).toEqual('id3');
expect(tasks[2].task).toEqual('test3') expect(tasks[2].task).toEqual('test3');
expect(tasks[3].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate()) expect(tasks[3].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[3].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate()) expect(tasks[3].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[3].renderEndTime).toBeNull() // Fixed end expect(tasks[3].renderEndTime).toBeNull(); // Fixed end
expect(tasks[3].id).toEqual('id4') expect(tasks[3].id).toEqual('id4');
expect(tasks[3].task).toEqual('test4') expect(tasks[3].task).toEqual('test4');
expect(tasks[4].startTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate()) expect(tasks[4].startTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
expect(tasks[4].endTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate()) expect(tasks[4].endTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate());
expect(tasks[4].renderEndTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate()) expect(tasks[4].renderEndTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate());
expect(tasks[4].id).toEqual('id5') expect(tasks[4].id).toEqual('id5');
expect(tasks[4].task).toEqual('test5') expect(tasks[4].task).toEqual('test5');
expect(tasks[5].startTime).toEqual(moment('2019-02-13', 'YYYY-MM-DD').toDate()) expect(tasks[5].startTime).toEqual(moment('2019-02-13', 'YYYY-MM-DD').toDate());
expect(tasks[5].endTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate()) expect(tasks[5].endTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate());
expect(tasks[5].renderEndTime).toEqual(moment('2019-02-15', 'YYYY-MM-DD').toDate()) expect(tasks[5].renderEndTime).toEqual(moment('2019-02-15', 'YYYY-MM-DD').toDate());
expect(tasks[5].id).toEqual('id6') expect(tasks[5].id).toEqual('id6');
expect(tasks[5].task).toEqual('test6') expect(tasks[5].task).toEqual('test6');
expect(tasks[6].startTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate()) expect(tasks[6].startTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate());
expect(tasks[6].endTime).toEqual(moment('2019-02-19', 'YYYY-MM-DD').toDate()) expect(tasks[6].endTime).toEqual(moment('2019-02-19', 'YYYY-MM-DD').toDate());
expect(tasks[6].id).toEqual('id7') expect(tasks[6].id).toEqual('id7');
expect(tasks[6].task).toEqual('test7') expect(tasks[6].task).toEqual('test7');
}) });
describe('when setting inclusive end dates', function () { describe('when setting inclusive end dates', function() {
beforeEach(function () { beforeEach(function() {
ganttDb.setDateFormat('YYYY-MM-DD') ganttDb.setDateFormat('YYYY-MM-DD');
ganttDb.enableInclusiveEndDates() ganttDb.enableInclusiveEndDates();
ganttDb.addTask('test1', 'id1,2019-02-01,1d') ganttDb.addTask('test1', 'id1,2019-02-01,1d');
ganttDb.addTask('test2', 'id2,2019-02-01,2019-02-03') ganttDb.addTask('test2', 'id2,2019-02-01,2019-02-03');
}) });
it('should automatically add one day to all end dates', function () { it('should automatically add one day to all end dates', function() {
const tasks = ganttDb.getTasks() const tasks = ganttDb.getTasks();
expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate()) expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[0].endTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate()) expect(tasks[0].endTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate());
expect(tasks[0].id).toEqual('id1') expect(tasks[0].id).toEqual('id1');
expect(tasks[0].task).toEqual('test1') expect(tasks[0].task).toEqual('test1');
expect(tasks[1].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate()) expect(tasks[1].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
expect(tasks[1].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate()) expect(tasks[1].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate());
expect(tasks[1].renderEndTime).toBeNull() // Fixed end expect(tasks[1].renderEndTime).toBeNull(); // Fixed end
expect(tasks[1].manualEndTime).toBeTruthy() expect(tasks[1].manualEndTime).toBeTruthy();
expect(tasks[1].id).toEqual('id2') expect(tasks[1].id).toEqual('id2');
expect(tasks[1].task).toEqual('test2') expect(tasks[1].task).toEqual('test2');
}) });
}) });
}) });

View File

@@ -1,9 +1,9 @@
import * as d3 from 'd3' import * as d3 from 'd3';
import { parser } from './parser/gantt' import { parser } from './parser/gantt';
import ganttDb from './ganttDb' import ganttDb from './ganttDb';
parser.yy = ganttDb parser.yy = ganttDb;
const conf = { const conf = {
titleTopMargin: 25, titleTopMargin: 25,
@@ -15,287 +15,316 @@ const conf = {
gridLineStartPadding: 35, gridLineStartPadding: 35,
fontSize: 11, fontSize: 11,
fontFamily: '"Open-Sans", "sans-serif"' fontFamily: '"Open-Sans", "sans-serif"'
} };
export const setConf = function (cnf) { export const setConf = function(cnf) {
const keys = Object.keys(cnf) const keys = Object.keys(cnf);
keys.forEach(function (key) { keys.forEach(function(key) {
conf[key] = cnf[key] conf[key] = cnf[key];
}) });
} };
let w let w;
export const draw = function (text, id) { export const draw = function(text, id) {
parser.yy.clear() parser.yy.clear();
parser.parse(text) parser.parse(text);
const elem = document.getElementById(id) const elem = document.getElementById(id);
w = elem.parentElement.offsetWidth w = elem.parentElement.offsetWidth;
if (typeof w === 'undefined') { if (typeof w === 'undefined') {
w = 1200 w = 1200;
} }
if (typeof conf.useWidth !== 'undefined') { if (typeof conf.useWidth !== 'undefined') {
w = conf.useWidth w = conf.useWidth;
} }
const taskArray = parser.yy.getTasks() const taskArray = parser.yy.getTasks();
// Set height based on number of tasks // Set height based on number of tasks
const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding;
elem.setAttribute('height', '100%') elem.setAttribute('height', '100%');
// Set viewBox // Set viewBox
elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h) elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
const svg = d3.select(`[id="${id}"]`) const svg = d3.select(`[id="${id}"]`);
// Set timescale // Set timescale
const timeScale = d3.scaleTime() const timeScale = d3
.domain([d3.min(taskArray, function (d) { .scaleTime()
return d.startTime .domain([
d3.min(taskArray, function(d) {
return d.startTime;
}), }),
d3.max(taskArray, function (d) { d3.max(taskArray, function(d) {
return d.endTime return d.endTime;
})]) })
.rangeRound([0, w - conf.leftPadding - conf.rightPadding]) ])
.rangeRound([0, w - conf.leftPadding - conf.rightPadding]);
let categories = [] let categories = [];
for (let i = 0; i < taskArray.length; i++) { for (let i = 0; i < taskArray.length; i++) {
categories.push(taskArray[i].type) categories.push(taskArray[i].type);
} }
const catsUnfiltered = categories // for vert labels const catsUnfiltered = categories; // for vert labels
categories = checkUnique(categories) categories = checkUnique(categories);
makeGant(taskArray, w, h) makeGant(taskArray, w, h);
if (typeof conf.useWidth !== 'undefined') { if (typeof conf.useWidth !== 'undefined') {
elem.setAttribute('width', w) elem.setAttribute('width', w);
} }
svg.append('text') svg
.append('text')
.text(parser.yy.getTitle()) .text(parser.yy.getTitle())
.attr('x', w / 2) .attr('x', w / 2)
.attr('y', conf.titleTopMargin) .attr('y', conf.titleTopMargin)
.attr('class', 'titleText') .attr('class', 'titleText');
function makeGant (tasks, pageWidth, pageHeight) { function makeGant(tasks, pageWidth, pageHeight) {
const barHeight = conf.barHeight const barHeight = conf.barHeight;
const gap = barHeight + conf.barGap const gap = barHeight + conf.barGap;
const topPadding = conf.topPadding const topPadding = conf.topPadding;
const leftPadding = conf.leftPadding const leftPadding = conf.leftPadding;
const colorScale = d3.scaleLinear() const colorScale = d3
.scaleLinear()
.domain([0, categories.length]) .domain([0, categories.length])
.range(['#00B9FA', '#F95002']) .range(['#00B9FA', '#F95002'])
.interpolate(d3.interpolateHcl) .interpolate(d3.interpolateHcl);
makeGrid(leftPadding, topPadding, pageWidth, pageHeight) makeGrid(leftPadding, topPadding, pageWidth, pageHeight);
drawRects(tasks, gap, topPadding, leftPadding, barHeight, colorScale, pageWidth, pageHeight) drawRects(tasks, gap, topPadding, leftPadding, barHeight, colorScale, pageWidth, pageHeight);
vertLabels(gap, topPadding, leftPadding, barHeight, colorScale) vertLabels(gap, topPadding, leftPadding, barHeight, colorScale);
drawToday(leftPadding, topPadding, pageWidth, pageHeight) drawToday(leftPadding, topPadding, pageWidth, pageHeight);
} }
function drawRects (theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h) { function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h) {
// Draw background rects covering the entire width of the graph, these form the section rows. // Draw background rects covering the entire width of the graph, these form the section rows.
svg.append('g') svg
.append('g')
.selectAll('rect') .selectAll('rect')
.data(theArray) .data(theArray)
.enter() .enter()
.append('rect') .append('rect')
.attr('x', 0) .attr('x', 0)
.attr('y', function (d, i) { .attr('y', function(d, i) {
return i * theGap + theTopPad - 2 return i * theGap + theTopPad - 2;
}) })
.attr('width', function () { .attr('width', function() {
return w - conf.rightPadding / 2 return w - conf.rightPadding / 2;
}) })
.attr('height', theGap) .attr('height', theGap)
.attr('class', function (d) { .attr('class', function(d) {
for (let i = 0; i < categories.length; i++) { for (let i = 0; i < categories.length; i++) {
if (d.type === categories[i]) { if (d.type === categories[i]) {
return 'section section' + (i % conf.numberSectionStyles) return 'section section' + (i % conf.numberSectionStyles);
} }
} }
return 'section section0' return 'section section0';
}) });
// Draw the rects representing the tasks // Draw the rects representing the tasks
const rectangles = svg.append('g') const rectangles = svg
.append('g')
.selectAll('rect') .selectAll('rect')
.data(theArray) .data(theArray)
.enter() .enter();
rectangles.append('rect') rectangles
.attr('id', function (d) { return d.id }) .append('rect')
.attr('id', function(d) {
return d.id;
})
.attr('rx', 3) .attr('rx', 3)
.attr('ry', 3) .attr('ry', 3)
.attr('x', function (d) { .attr('x', function(d) {
if (d.milestone) { if (d.milestone) {
return timeScale(d.startTime) + theSidePad + (0.5 * (timeScale(d.endTime) - timeScale(d.startTime))) - (0.5 * theBarHeight) return (
timeScale(d.startTime) +
theSidePad +
0.5 * (timeScale(d.endTime) - timeScale(d.startTime)) -
0.5 * theBarHeight
);
} }
return timeScale(d.startTime) + theSidePad return timeScale(d.startTime) + theSidePad;
}) })
.attr('y', function (d, i) { .attr('y', function(d, i) {
return i * theGap + theTopPad return i * theGap + theTopPad;
}) })
.attr('width', function (d) { .attr('width', function(d) {
if (d.milestone) { if (d.milestone) {
return theBarHeight return theBarHeight;
} }
return (timeScale(d.renderEndTime || d.endTime) - timeScale(d.startTime)) return timeScale(d.renderEndTime || d.endTime) - timeScale(d.startTime);
}) })
.attr('height', theBarHeight) .attr('height', theBarHeight)
.attr('transform-origin', function (d, i) { .attr('transform-origin', function(d, i) {
return (timeScale(d.startTime) + theSidePad + 0.5 * (timeScale(d.endTime) - timeScale(d.startTime))).toString() + 'px ' + (i * theGap + theTopPad + 0.5 * theBarHeight).toString() + 'px' return (
(
timeScale(d.startTime) +
theSidePad +
0.5 * (timeScale(d.endTime) - timeScale(d.startTime))
).toString() +
'px ' +
(i * theGap + theTopPad + 0.5 * theBarHeight).toString() +
'px'
);
}) })
.attr('class', function (d) { .attr('class', function(d) {
const res = 'task' const res = 'task';
let classStr = '' let classStr = '';
if (d.classes.length > 0) { if (d.classes.length > 0) {
classStr = d.classes.join(' ') classStr = d.classes.join(' ');
} }
let secNum = 0 let secNum = 0;
for (let i = 0; i < categories.length; i++) { for (let i = 0; i < categories.length; i++) {
if (d.type === categories[i]) { if (d.type === categories[i]) {
secNum = (i % conf.numberSectionStyles) secNum = i % conf.numberSectionStyles;
} }
} }
let taskClass = '' let taskClass = '';
if (d.active) { if (d.active) {
if (d.crit) { if (d.crit) {
taskClass += ' activeCrit' taskClass += ' activeCrit';
} else { } else {
taskClass = ' active' taskClass = ' active';
} }
} else if (d.done) { } else if (d.done) {
if (d.crit) { if (d.crit) {
taskClass = ' doneCrit' taskClass = ' doneCrit';
} else { } else {
taskClass = ' done' taskClass = ' done';
} }
} else { } else {
if (d.crit) { if (d.crit) {
taskClass += ' crit' taskClass += ' crit';
} }
} }
if (taskClass.length === 0) { if (taskClass.length === 0) {
taskClass = ' task' taskClass = ' task';
} }
if (d.milestone) { if (d.milestone) {
taskClass = ' milestone ' + taskClass taskClass = ' milestone ' + taskClass;
} }
taskClass += secNum taskClass += secNum;
taskClass += ' ' + classStr taskClass += ' ' + classStr;
return res + taskClass return res + taskClass;
}) });
// Append task labels // Append task labels
rectangles.append('text') rectangles
.attr('id', function (d) { return d.id + '-text' }) .append('text')
.text(function (d) { .attr('id', function(d) {
return d.task return d.id + '-text';
})
.text(function(d) {
return d.task;
}) })
.attr('font-size', conf.fontSize) .attr('font-size', conf.fontSize)
.attr('x', function (d) { .attr('x', function(d) {
let startX = timeScale(d.startTime) let startX = timeScale(d.startTime);
let endX = timeScale(d.renderEndTime || d.endTime) let endX = timeScale(d.renderEndTime || d.endTime);
if (d.milestone) { if (d.milestone) {
startX += (0.5 * (timeScale(d.endTime) - timeScale(d.startTime))) - (0.5 * theBarHeight) startX += 0.5 * (timeScale(d.endTime) - timeScale(d.startTime)) - 0.5 * theBarHeight;
} }
if (d.milestone) { if (d.milestone) {
endX = startX + theBarHeight endX = startX + theBarHeight;
} }
const textWidth = this.getBBox().width const textWidth = this.getBBox().width;
// Check id text width > width of rectangle // Check id text width > width of rectangle
if (textWidth > (endX - startX)) { if (textWidth > endX - startX) {
if (endX + textWidth + 1.5 * conf.leftPadding > w) { if (endX + textWidth + 1.5 * conf.leftPadding > w) {
return startX + theSidePad - 5 return startX + theSidePad - 5;
} else { } else {
return endX + theSidePad + 5 return endX + theSidePad + 5;
} }
} else { } else {
return (endX - startX) / 2 + startX + theSidePad return (endX - startX) / 2 + startX + theSidePad;
} }
}) })
.attr('y', function (d, i) { .attr('y', function(d, i) {
return i * theGap + (conf.barHeight / 2) + (conf.fontSize / 2 - 2) + theTopPad return i * theGap + conf.barHeight / 2 + (conf.fontSize / 2 - 2) + theTopPad;
}) })
.attr('text-height', theBarHeight) .attr('text-height', theBarHeight)
.attr('class', function (d) { .attr('class', function(d) {
const startX = timeScale(d.startTime) const startX = timeScale(d.startTime);
let endX = timeScale(d.endTime) let endX = timeScale(d.endTime);
if (d.milestone) { if (d.milestone) {
endX = startX + theBarHeight endX = startX + theBarHeight;
} }
const textWidth = this.getBBox().width const textWidth = this.getBBox().width;
let classStr = '' let classStr = '';
if (d.classes.length > 0) { if (d.classes.length > 0) {
classStr = d.classes.join(' ') classStr = d.classes.join(' ');
} }
let secNum = 0 let secNum = 0;
for (let i = 0; i < categories.length; i++) { for (let i = 0; i < categories.length; i++) {
if (d.type === categories[i]) { if (d.type === categories[i]) {
secNum = (i % conf.numberSectionStyles) secNum = i % conf.numberSectionStyles;
} }
} }
let taskType = '' let taskType = '';
if (d.active) { if (d.active) {
if (d.crit) { if (d.crit) {
taskType = 'activeCritText' + secNum taskType = 'activeCritText' + secNum;
} else { } else {
taskType = 'activeText' + secNum taskType = 'activeText' + secNum;
} }
} }
if (d.done) { if (d.done) {
if (d.crit) { if (d.crit) {
taskType = taskType + ' doneCritText' + secNum taskType = taskType + ' doneCritText' + secNum;
} else { } else {
taskType = taskType + ' doneText' + secNum taskType = taskType + ' doneText' + secNum;
} }
} else { } else {
if (d.crit) { if (d.crit) {
taskType = taskType + ' critText' + secNum taskType = taskType + ' critText' + secNum;
} }
} }
if (d.milestone) { if (d.milestone) {
taskType += ' milestoneText' taskType += ' milestoneText';
} }
// Check id text width > width of rectangle // Check id text width > width of rectangle
if (textWidth > (endX - startX)) { if (textWidth > endX - startX) {
if (endX + textWidth + 1.5 * conf.leftPadding > w) { if (endX + textWidth + 1.5 * conf.leftPadding > w) {
return classStr + ' taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType return classStr + ' taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType;
} else { } else {
return classStr + ' taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType return classStr + ' taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType;
} }
} else { } else {
return classStr + ' taskText taskText' + secNum + ' ' + taskType return classStr + ' taskText taskText' + secNum + ' ' + taskType;
} }
}) });
} }
function makeGrid (theSidePad, theTopPad, w, h) { function makeGrid(theSidePad, theTopPad, w, h) {
let xAxis = d3.axisBottom(timeScale) let xAxis = d3
.axisBottom(timeScale)
.tickSize(-h + theTopPad + conf.gridLineStartPadding) .tickSize(-h + theTopPad + conf.gridLineStartPadding)
.tickFormat(d3.timeFormat(parser.yy.getAxisFormat() || conf.axisFormat || '%Y-%m-%d')) .tickFormat(d3.timeFormat(parser.yy.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'));
svg.append('g') svg
.append('g')
.attr('class', 'grid') .attr('class', 'grid')
.attr('transform', 'translate(' + theSidePad + ', ' + (h - 50) + ')') .attr('transform', 'translate(' + theSidePad + ', ' + (h - 50) + ')')
.call(xAxis) .call(xAxis)
@@ -304,90 +333,92 @@ export const draw = function (text, id) {
.attr('fill', '#000') .attr('fill', '#000')
.attr('stroke', 'none') .attr('stroke', 'none')
.attr('font-size', 10) .attr('font-size', 10)
.attr('dy', '1em') .attr('dy', '1em');
} }
function vertLabels (theGap, theTopPad) { function vertLabels(theGap, theTopPad) {
const numOccurances = [] const numOccurances = [];
let prevGap = 0 let prevGap = 0;
for (let i = 0; i < categories.length; i++) { for (let i = 0; i < categories.length; i++) {
numOccurances[i] = [categories[i], getCount(categories[i], catsUnfiltered)] numOccurances[i] = [categories[i], getCount(categories[i], catsUnfiltered)];
} }
svg.append('g') // without doing this, impossible to put grid lines behind text svg
.append('g') // without doing this, impossible to put grid lines behind text
.selectAll('text') .selectAll('text')
.data(numOccurances) .data(numOccurances)
.enter() .enter()
.append('text') .append('text')
.text(function (d) { .text(function(d) {
return d[0] return d[0];
}) })
.attr('x', 10) .attr('x', 10)
.attr('y', function (d, i) { .attr('y', function(d, i) {
if (i > 0) { if (i > 0) {
for (let j = 0; j < i; j++) { for (let j = 0; j < i; j++) {
prevGap += numOccurances[i - 1][1] prevGap += numOccurances[i - 1][1];
return d[1] * theGap / 2 + prevGap * theGap + theTopPad return (d[1] * theGap) / 2 + prevGap * theGap + theTopPad;
} }
} else { } else {
return d[1] * theGap / 2 + theTopPad return (d[1] * theGap) / 2 + theTopPad;
} }
}) })
.attr('class', function (d) { .attr('class', function(d) {
for (let i = 0; i < categories.length; i++) { for (let i = 0; i < categories.length; i++) {
if (d[0] === categories[i]) { if (d[0] === categories[i]) {
return 'sectionTitle sectionTitle' + (i % conf.numberSectionStyles) return 'sectionTitle sectionTitle' + (i % conf.numberSectionStyles);
} }
} }
return 'sectionTitle' return 'sectionTitle';
}) });
} }
function drawToday (theSidePad, theTopPad, w, h) { function drawToday(theSidePad, theTopPad, w, h) {
const todayG = svg.append('g') const todayG = svg.append('g').attr('class', 'today');
.attr('class', 'today')
const today = new Date() const today = new Date();
todayG.append('line') todayG
.append('line')
.attr('x1', timeScale(today) + theSidePad) .attr('x1', timeScale(today) + theSidePad)
.attr('x2', timeScale(today) + theSidePad) .attr('x2', timeScale(today) + theSidePad)
.attr('y1', conf.titleTopMargin) .attr('y1', conf.titleTopMargin)
.attr('y2', h - conf.titleTopMargin) .attr('y2', h - conf.titleTopMargin)
.attr('class', 'today') .attr('class', 'today');
} }
// from this stackexchange question: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript // from this stackexchange question: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript
function checkUnique (arr) { function checkUnique(arr) {
const hash = {} const hash = {};
const result = [] const result = [];
for (let i = 0, l = arr.length; i < l; ++i) { for (let i = 0, l = arr.length; i < l; ++i) {
if (!hash.hasOwnProperty(arr[i])) { // it works with objects! in FF, at least if (!hash.hasOwnProperty(arr[i])) {
hash[arr[i]] = true // it works with objects! in FF, at least
result.push(arr[i]) hash[arr[i]] = true;
result.push(arr[i]);
} }
} }
return result return result;
} }
// from this stackexchange question: http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array // from this stackexchange question: http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array
function getCounts (arr) { function getCounts(arr) {
let i = arr.length // const to loop over let i = arr.length; // const to loop over
const obj = {} // obj to store results const obj = {}; // obj to store results
while (i) { while (i) {
obj[arr[--i]] = (obj[arr[i]] || 0) + 1 // count occurrences obj[arr[--i]] = (obj[arr[i]] || 0) + 1; // count occurrences
} }
return obj return obj;
} }
// get specific from everything // get specific from everything
function getCount (word, arr) { function getCount(word, arr) {
return getCounts(arr)[word] || 0 return getCounts(arr)[word] || 0;
} }
} };
export default { export default {
setConf, setConf,
draw draw
} };

View File

@@ -1,50 +1,52 @@
/* eslint-env jasmine */ /* eslint-env jasmine */
/* eslint-disable no-eval */ /* eslint-disable no-eval */
import { parser } from './gantt' import { parser } from './gantt';
import ganttDb from '../ganttDb' import ganttDb from '../ganttDb';
const parserFnConstructor = (str) => { const parserFnConstructor = str => {
return () => { return () => {
parser.parse(str) parser.parse(str);
} };
} };
describe('when parsing a gantt diagram it', function () { describe('when parsing a gantt diagram it', function() {
beforeEach(function () { beforeEach(function() {
parser.yy = ganttDb parser.yy = ganttDb;
parser.yy.clear() parser.yy.clear();
}) });
it('should handle a dateFormat definition', function () { it('should handle a dateFormat definition', function() {
const str = 'gantt\ndateFormat yyyy-mm-dd' const str = 'gantt\ndateFormat yyyy-mm-dd';
expect(parserFnConstructor(str)).not.toThrow() expect(parserFnConstructor(str)).not.toThrow();
}) });
it('should handle a inclusive end date definition', function () { it('should handle a inclusive end date definition', function() {
const str = 'gantt\ndateFormat yyyy-mm-dd\ninclusiveEndDates' const str = 'gantt\ndateFormat yyyy-mm-dd\ninclusiveEndDates';
expect(parserFnConstructor(str)).not.toThrow() expect(parserFnConstructor(str)).not.toThrow();
}) });
it('should handle a title definition', function () { it('should handle a title definition', function() {
const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid' const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid';
expect(parserFnConstructor(str)).not.toThrow() expect(parserFnConstructor(str)).not.toThrow();
}) });
it('should handle an excludes definition', function () { it('should handle an excludes definition', function() {
const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid\nexcludes weekdays 2019-02-01' const str =
'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid\nexcludes weekdays 2019-02-01';
expect(parserFnConstructor(str)).not.toThrow() expect(parserFnConstructor(str)).not.toThrow();
}) });
it('should handle a section definition', function () { it('should handle a section definition', function() {
const str = 'gantt\n' + const str =
'gantt\n' +
'dateFormat yyyy-mm-dd\n' + 'dateFormat yyyy-mm-dd\n' +
'title Adding gantt diagram functionality to mermaid\n' + 'title Adding gantt diagram functionality to mermaid\n' +
'excludes weekdays 2019-02-01\n' + 'excludes weekdays 2019-02-01\n' +
'section Documentation' 'section Documentation';
expect(parserFnConstructor(str)).not.toThrow() expect(parserFnConstructor(str)).not.toThrow();
}) });
/** /**
* Beslutsflöde inligt nedan. Obs bla bla bla * Beslutsflöde inligt nedan. Obs bla bla bla
* ``` * ```
@@ -56,22 +58,23 @@ describe('when parsing a gantt diagram it', function () {
``` ```
* params bapa - a unique bapap * params bapa - a unique bapap
*/ */
it('should handle a task definition', function () { it('should handle a task definition', function() {
const str = 'gantt\n' + const str =
'gantt\n' +
'dateFormat YYYY-MM-DD\n' + 'dateFormat YYYY-MM-DD\n' +
'title Adding gantt diagram functionality to mermaid\n' + 'title Adding gantt diagram functionality to mermaid\n' +
'section Documentation\n' + 'section Documentation\n' +
'Design jison grammar:des1, 2014-01-01, 2014-01-04' 'Design jison grammar:des1, 2014-01-01, 2014-01-04';
expect(parserFnConstructor(str)).not.toThrow() expect(parserFnConstructor(str)).not.toThrow();
const tasks = parser.yy.getTasks() const tasks = parser.yy.getTasks();
expect(tasks[0].startTime).toEqual(new Date(2014, 0, 1)) expect(tasks[0].startTime).toEqual(new Date(2014, 0, 1));
expect(tasks[0].endTime).toEqual(new Date(2014, 0, 4)) expect(tasks[0].endTime).toEqual(new Date(2014, 0, 4));
expect(tasks[0].id).toEqual('des1') expect(tasks[0].id).toEqual('des1');
expect(tasks[0].task).toEqual('Design jison grammar') expect(tasks[0].task).toEqual('Design jison grammar');
}) });
it.each` it.each`
tags | milestone | done | crit | active tags | milestone | done | crit | active
${'milestone'} | ${true} | ${false} | ${false} | ${false} ${'milestone'} | ${true} | ${false} | ${false} | ${false}
@@ -80,24 +83,27 @@ describe('when parsing a gantt diagram it', function () {
${'active'} | ${false} | ${false} | ${false} | ${true} ${'active'} | ${false} | ${false} | ${false} | ${true}
${'crit,milestone,done'} | ${true} | ${true} | ${true} | ${false} ${'crit,milestone,done'} | ${true} | ${true} | ${true} | ${false}
`('should handle a task with tags $tags', ({ tags, milestone, done, crit, active }) => { `('should handle a task with tags $tags', ({ tags, milestone, done, crit, active }) => {
const str = 'gantt\n' + const str =
'gantt\n' +
'dateFormat YYYY-MM-DD\n' + 'dateFormat YYYY-MM-DD\n' +
'title Adding gantt diagram functionality to mermaid\n' + 'title Adding gantt diagram functionality to mermaid\n' +
'section Documentation\n' + 'section Documentation\n' +
'test task:' + tags + ', 2014-01-01, 2014-01-04' 'test task:' +
tags +
', 2014-01-01, 2014-01-04';
const allowedTags = ['active', 'done', 'crit', 'milestone'] const allowedTags = ['active', 'done', 'crit', 'milestone'];
expect(parserFnConstructor(str)).not.toThrow() expect(parserFnConstructor(str)).not.toThrow();
const tasks = parser.yy.getTasks() const tasks = parser.yy.getTasks();
allowedTags.forEach(function (t) { allowedTags.forEach(function(t) {
if (eval(t)) { if (eval(t)) {
expect(tasks[0][t]).toBeTruthy() expect(tasks[0][t]).toBeTruthy();
} else { } else {
expect(tasks[0][t]).toBeFalsy() expect(tasks[0][t]).toBeFalsy();
} }
}) });
}) });
}) });