mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-16 13:59:54 +02:00
#931 Updating code to align witn new code standard
This commit is contained in:
@@ -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
|
||||||
@@ -224,114 +221,114 @@ const parseId = function (idStr) {
|
|||||||
// 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,
|
||||||
@@ -342,28 +339,28 @@ 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 = {
|
||||||
@@ -372,56 +369,65 @@ export const addTaskOrg = function (descr, data) {
|
|||||||
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.
|
||||||
@@ -429,18 +435,20 @@ const compileTasks = function () {
|
|||||||
* @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.
|
||||||
@@ -449,41 +457,43 @@ export const setLink = function (ids, _linkStr) {
|
|||||||
*/
|
*/
|
||||||
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
|
||||||
@@ -493,23 +503,23 @@ const setClickFun = function (id, functionName, functionArgs) {
|
|||||||
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.
|
||||||
@@ -519,10 +529,10 @@ const pushFun = function (id, callbackFunction) {
|
|||||||
*/
|
*/
|
||||||
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
|
||||||
@@ -530,9 +540,9 @@ export const setClickEvent = function (ids, functionName, functionArgs) {
|
|||||||
*/
|
*/
|
||||||
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;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
/* 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`
|
||||||
@@ -13,20 +13,20 @@ describe('when using the ganttDb', function () {
|
|||||||
${' 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
|
||||||
@@ -53,134 +53,147 @@ describe('when using the ganttDb', function () {
|
|||||||
${'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');
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
@@ -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')
|
||||||
|
.attr('id', function(d) {
|
||||||
|
return d.id + '-text';
|
||||||
|
})
|
||||||
.text(function(d) {
|
.text(function(d) {
|
||||||
return d.task
|
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
|
||||||
}
|
};
|
||||||
|
@@ -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
|
||||||
* ```
|
* ```
|
||||||
@@ -57,21 +59,22 @@ 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();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
Reference in New Issue
Block a user