mermaid/src/diagrams/gantt/ganttDb.js

553 lines
13 KiB
JavaScript
Raw Normal View History

import moment from 'moment-mini'
2017-09-10 19:41:34 +08:00
import { logger } from '../../logger'
import * as d3 from 'd3'
2017-04-11 22:14:25 +08:00
2017-09-14 20:59:58 +08:00
let dateFormat = ''
let axisFormat = ''
2019-02-06 16:54:09 -02:00
let excludes = []
2017-09-14 20:59:58 +08:00
let title = ''
let sections = []
let tasks = []
let currentSection = ''
const tags = ['active', 'done', 'crit', 'milestone']
let funs = []
let inclusiveEndDates = false
2017-04-11 22:14:25 +08:00
2017-09-10 21:23:04 +08:00
export const clear = function () {
2017-04-11 22:14:25 +08:00
sections = []
tasks = []
currentSection = ''
funs = []
2017-04-11 22:14:25 +08:00
title = ''
taskCnt = 0
lastTask = undefined
lastTaskID = undefined
rawTasks = []
dateFormat = ''
axisFormat = ''
excludes = []
inclusiveEndDates = false
2017-04-11 22:14:25 +08:00
}
export const setAxisFormat = function (txt) {
axisFormat = txt
}
export const getAxisFormat = function () {
return axisFormat
}
export const setDateFormat = function (txt, inclusive) {
2017-04-11 22:14:25 +08:00
dateFormat = txt
inclusiveEndDates = inclusive || false // make sure it's not undefined
2017-04-11 22:14:25 +08:00
}
export const getDateFormat = function () {
return dateFormat
}
2019-02-06 16:54:09 -02:00
export const setExcludes = function (txt) {
excludes = txt.toLowerCase().split(/[\s,]+/)
2019-02-06 16:54:09 -02:00
}
export const getExcludes = function () {
return excludes
}
2017-09-10 21:23:04 +08:00
export const setTitle = function (txt) {
2017-04-11 22:14:25 +08:00
title = txt
}
2017-09-10 21:23:04 +08:00
export const getTitle = function () {
2017-04-11 22:14:25 +08:00
return title
}
2017-09-10 21:23:04 +08:00
export const addSection = function (txt) {
2017-04-11 22:14:25 +08:00
currentSection = txt
sections.push(txt)
}
export const getSections = function () {
return sections
}
2017-09-10 21:23:04 +08:00
export const getTasks = function () {
2017-09-14 20:59:58 +08:00
let allItemsPricessed = compileTasks()
const maxDepth = 10
let iterationCount = 0
2017-04-11 22:14:25 +08:00
while (!allItemsPricessed && (iterationCount < maxDepth)) {
allItemsPricessed = compileTasks()
iterationCount++
}
tasks = rawTasks
return tasks
}
2015-02-08 20:07:15 +01:00
2019-02-06 17:24:20 -02:00
const isInvalidDate = function (date, dateFormat, excludes) {
2019-02-06 17:25:39 -02:00
if (date.isoWeekday() >= 6 && excludes.indexOf('weekends') >= 0) {
return true
}
if (excludes.indexOf(date.format('dddd').toLowerCase()) >= 0) {
return true
}
2019-02-06 17:25:39 -02:00
return excludes.indexOf(date.format(dateFormat.trim())) >= 0
2019-02-06 17:24:20 -02:00
}
2019-02-06 17:09:59 -02:00
2019-02-12 17:40:57 -02:00
const checkTaskDates = function (task, dateFormat, excludes) {
2019-02-07 12:05:09 -02:00
if (!excludes.length || task.manualEndTime) return
2019-02-12 17:52:18 -02:00
let startTime = moment(task.startTime, dateFormat, true)
startTime.add(1, 'd')
let endTime = moment(task.endTime, dateFormat, true)
let renderEndTime = fixTaskDates(startTime, endTime, dateFormat, excludes)
task.endTime = endTime.toDate()
task.renderEndTime = renderEndTime
2019-02-12 17:40:57 -02:00
}
2019-02-12 17:52:18 -02:00
const fixTaskDates = function (startTime, endTime, dateFormat, excludes) {
2019-02-12 17:29:38 -02:00
let invalid = false
2019-02-12 17:52:18 -02:00
let renderEndTime = null
2019-02-07 11:27:01 -02:00
while (startTime.date() <= endTime.date()) {
2019-02-12 17:29:38 -02:00
if (!invalid) {
2019-02-12 17:52:18 -02:00
renderEndTime = endTime.toDate()
2019-02-12 17:29:38 -02:00
}
invalid = isInvalidDate(startTime, dateFormat, excludes)
if (invalid) {
2019-02-07 11:27:01 -02:00
endTime.add(1, 'd')
}
2019-02-07 11:27:01 -02:00
startTime.add(1, 'd')
2019-02-06 16:54:09 -02:00
}
2019-02-12 17:52:18 -02:00
return renderEndTime
2019-02-06 16:54:09 -02:00
}
const getStartDate = function (prevTime, dateFormat, str) {
2017-04-11 22:14:25 +08:00
str = str.trim()
2017-04-16 23:48:36 +08:00
// Test for after
2017-09-14 20:59:58 +08:00
const re = /^after\s+([\d\w-]+)/
const afterStatement = re.exec(str.trim())
2015-10-21 21:14:41 +02:00
2017-04-11 22:14:25 +08:00
if (afterStatement !== null) {
2017-09-14 20:59:58 +08:00
const task = findTaskById(afterStatement[1])
2017-04-11 22:14:25 +08:00
if (typeof task === 'undefined') {
2017-09-14 20:59:58 +08:00
const dt = new Date()
2017-04-11 22:14:25 +08:00
dt.setHours(0, 0, 0, 0)
return dt
2015-02-08 20:07:15 +01:00
}
2017-04-11 22:14:25 +08:00
return task.endTime
}
2017-04-16 23:48:36 +08:00
// Check for actual date set
2019-02-06 17:09:59 -02:00
let mDate = moment(str, dateFormat.trim(), true)
2019-02-06 16:54:09 -02:00
if (mDate.isValid()) {
return mDate.toDate()
2017-04-11 22:14:25 +08:00
} else {
2017-09-10 19:41:34 +08:00
logger.debug('Invalid date:' + str)
logger.debug('With date format:' + dateFormat.trim())
2017-04-11 22:14:25 +08:00
}
2017-04-16 23:48:36 +08:00
// Default date - now
return new Date()
2017-04-11 22:14:25 +08:00
}
2015-02-08 20:07:15 +01:00
const getEndDate = function (prevTime, dateFormat, str, inclusive) {
inclusive = inclusive || false
2017-04-11 22:14:25 +08:00
str = str.trim()
2017-04-16 23:48:36 +08:00
// Check for actual date
2019-02-06 17:09:59 -02:00
let mDate = moment(str, dateFormat.trim(), true)
2019-02-06 16:54:09 -02:00
if (mDate.isValid()) {
if (inclusive) {
mDate.add(1, 'd')
}
2019-02-06 16:54:09 -02:00
return mDate.toDate()
2017-04-11 22:14:25 +08:00
}
2017-09-14 20:59:58 +08:00
const d = moment(prevTime)
2017-04-16 23:48:36 +08:00
// Check for length
2017-09-14 20:59:58 +08:00
const re = /^([\d]+)([wdhms])/
const durationStatement = re.exec(str.trim())
2017-04-11 22:14:25 +08:00
if (durationStatement !== null) {
switch (durationStatement[2]) {
case 's':
d.add(durationStatement[1], 'seconds')
break
case 'm':
d.add(durationStatement[1], 'minutes')
break
case 'h':
d.add(durationStatement[1], 'hours')
break
case 'd':
d.add(durationStatement[1], 'days')
break
case 'w':
d.add(durationStatement[1], 'weeks')
break
2015-02-08 20:07:15 +01:00
}
2017-04-11 22:14:25 +08:00
}
2017-04-16 23:48:36 +08:00
// Default date - now
return d.toDate()
2017-04-11 22:14:25 +08:00
}
2017-09-14 20:59:58 +08:00
let taskCnt = 0
const parseId = function (idStr) {
2017-04-11 22:14:25 +08:00
if (typeof idStr === 'undefined') {
taskCnt = taskCnt + 1
return 'task' + taskCnt
}
return idStr
}
2015-02-08 20:07:15 +01:00
// id, startDate, endDate
// id, startDate, length
// id, after x, endDate
// id, after x, length
// startDate, endDate
// startDate, length
// after x, endDate
// after x, length
// endDate
// length
2017-09-14 20:59:58 +08:00
const compileData = function (prevTask, dataStr) {
let ds
2017-04-11 22:14:25 +08:00
if (dataStr.substr(0, 1) === ':') {
ds = dataStr.substr(1, dataStr.length)
} else {
ds = dataStr
}
2017-09-14 20:59:58 +08:00
const data = ds.split(',')
2017-09-14 20:59:58 +08:00
const task = {}
2019-01-31 15:33:35 +08:00
// Get tags like active, done, crit and milestone
getTaskTags(data, task, tags)
2017-09-14 20:59:58 +08:00
for (let i = 0; i < data.length; i++) {
2017-04-11 22:14:25 +08:00
data[i] = data[i].trim()
}
let endTimeData = ''
2017-04-11 22:14:25 +08:00
switch (data.length) {
case 1:
task.id = parseId()
task.startTime = prevTask.endTime
endTimeData = data[0]
2017-04-11 22:14:25 +08:00
break
case 2:
task.id = parseId()
task.startTime = getStartDate(undefined, dateFormat, data[0])
endTimeData = data[1]
2017-04-11 22:14:25 +08:00
break
case 3:
task.id = parseId(data[0])
task.startTime = getStartDate(undefined, dateFormat, data[1])
endTimeData = data[2]
2017-04-11 22:14:25 +08:00
break
default:
}
if (endTimeData) {
task.endTime = getEndDate(task.startTime, dateFormat, endTimeData, inclusiveEndDates)
task.manualEndTime = moment(endTimeData, 'YYYY-MM-DD', true).isValid()
2019-02-12 17:40:57 -02:00
checkTaskDates(task, dateFormat, excludes)
}
2017-04-11 22:14:25 +08:00
return task
}
2017-09-14 20:59:58 +08:00
const parseData = function (prevTaskId, dataStr) {
let ds
2017-04-11 22:14:25 +08:00
if (dataStr.substr(0, 1) === ':') {
ds = dataStr.substr(1, dataStr.length)
} else {
ds = dataStr
}
2017-09-14 20:59:58 +08:00
const data = ds.split(',')
2017-04-11 22:14:25 +08:00
2017-09-14 20:59:58 +08:00
const task = {}
2015-10-21 21:14:41 +02:00
2019-01-31 15:33:35 +08:00
// Get tags like active, done, crit and milestone
getTaskTags(data, task, tags)
2017-09-14 20:59:58 +08:00
for (let i = 0; i < data.length; i++) {
2017-04-11 22:14:25 +08:00
data[i] = data[i].trim()
}
switch (data.length) {
case 1:
task.id = parseId()
task.startTime = {
type: 'prevTaskEnd',
id: prevTaskId
}
task.endTime = {
data: data[0]
}
2017-04-11 22:14:25 +08:00
break
case 2:
task.id = parseId()
task.startTime = {
type: 'getStartDate',
startData: data[0]
}
task.endTime = {
data: data[1]
}
2017-04-11 22:14:25 +08:00
break
case 3:
task.id = parseId(data[0])
task.startTime = {
type: 'getStartDate',
startData: data[1]
}
task.endTime = {
data: data[2]
}
2017-04-11 22:14:25 +08:00
break
default:
}
return task
}
2017-09-14 20:59:58 +08:00
let lastTask
let lastTaskID
let rawTasks = []
const taskDb = {}
2017-09-10 21:23:04 +08:00
export const addTask = function (descr, data) {
2017-09-14 20:59:58 +08:00
const rawTask = {
2017-04-11 22:14:25 +08:00
section: currentSection,
type: currentSection,
processed: false,
manualEndTime: false,
2019-02-12 17:29:38 -02:00
renderEndTime: null,
2017-04-16 23:48:36 +08:00
raw: { data: data },
task: descr,
classes: []
2017-04-11 22:14:25 +08:00
}
2017-09-14 20:59:58 +08:00
const taskInfo = parseData(lastTaskID, data)
2017-04-11 22:14:25 +08:00
rawTask.raw.startTime = taskInfo.startTime
rawTask.raw.endTime = taskInfo.endTime
rawTask.id = taskInfo.id
rawTask.prevTaskId = lastTaskID
rawTask.active = taskInfo.active
rawTask.done = taskInfo.done
rawTask.crit = taskInfo.crit
2019-01-31 15:33:35 +08:00
rawTask.milestone = taskInfo.milestone
2017-04-11 22:14:25 +08:00
2017-09-14 20:59:58 +08:00
const pos = rawTasks.push(rawTask)
2017-04-11 22:14:25 +08:00
lastTaskID = rawTask.id
2017-04-16 23:48:36 +08:00
// Store cross ref
2017-04-11 22:14:25 +08:00
taskDb[rawTask.id] = pos - 1
}
2017-09-10 21:23:04 +08:00
export const findTaskById = function (id) {
2017-09-14 20:59:58 +08:00
const pos = taskDb[id]
2017-04-11 22:14:25 +08:00
return rawTasks[pos]
}
2017-09-10 21:23:04 +08:00
export const addTaskOrg = function (descr, data) {
2017-09-14 20:59:58 +08:00
const newTask = {
2017-04-11 22:14:25 +08:00
section: currentSection,
type: currentSection,
description: descr,
task: descr,
classes: []
2017-04-11 22:14:25 +08:00
}
2017-09-14 20:59:58 +08:00
const taskInfo = compileData(lastTask, data)
2017-04-11 22:14:25 +08:00
newTask.startTime = taskInfo.startTime
newTask.endTime = taskInfo.endTime
newTask.id = taskInfo.id
newTask.active = taskInfo.active
newTask.done = taskInfo.done
newTask.crit = taskInfo.crit
2019-01-31 15:33:35 +08:00
newTask.milestone = taskInfo.milestone
2017-04-11 22:14:25 +08:00
lastTask = newTask
tasks.push(newTask)
}
2017-09-14 20:59:58 +08:00
const compileTasks = function () {
const compileTask = function (pos) {
const task = rawTasks[pos]
let startTime = ''
2017-04-11 22:14:25 +08:00
switch (rawTasks[pos].raw.startTime.type) {
case 'prevTaskEnd':
2017-09-14 20:59:58 +08:00
const prevTask = findTaskById(task.prevTaskId)
2017-04-11 22:14:25 +08:00
task.startTime = prevTask.endTime
break
case 'getStartDate':
startTime = getStartDate(undefined, dateFormat, rawTasks[pos].raw.startTime.startData)
2017-04-11 22:14:25 +08:00
if (startTime) {
rawTasks[pos].startTime = startTime
2015-10-21 21:14:41 +02:00
}
2017-04-11 22:14:25 +08:00
break
}
2015-10-21 21:14:41 +02:00
2017-04-11 22:14:25 +08:00
if (rawTasks[pos].startTime) {
rawTasks[pos].endTime = getEndDate(rawTasks[pos].startTime, dateFormat, rawTasks[pos].raw.endTime.data, inclusiveEndDates)
2017-04-11 22:14:25 +08:00
if (rawTasks[pos].endTime) {
rawTasks[pos].processed = true
rawTasks[pos].manualEndTime = moment(rawTasks[pos].raw.endTime.data, 'YYYY-MM-DD', true).isValid()
2019-02-12 17:40:57 -02:00
checkTaskDates(rawTasks[pos], dateFormat, excludes)
2017-04-11 22:14:25 +08:00
}
}
2017-04-11 22:14:25 +08:00
return rawTasks[pos].processed
}
2015-10-21 21:14:41 +02:00
2017-09-14 20:59:58 +08:00
let allProcessed = true
for (let i = 0; i < rawTasks.length; i++) {
2017-04-11 22:14:25 +08:00
compileTask(i)
2017-04-11 22:14:25 +08:00
allProcessed = allProcessed && rawTasks[i].processed
}
return allProcessed
}
2017-09-10 21:51:48 +08:00
/**
* Called by parser when a link is found. Adds the URL to the vertex data.
* @param ids Comma separated list of ids
* @param linkStr URL to create a link for
*/
export const setLink = function (ids, linkStr) {
ids.split(',').forEach(function (id) {
let rawTask = findTaskById(id)
if (typeof rawTask !== 'undefined') {
2019-03-09 23:17:13 +01:00
pushFun(id, () => { window.open(linkStr, '_self') })
}
})
setClass(ids, 'clickable')
}
/**
* Called by parser when a special node is found, e.g. a clickable element.
* @param ids Comma separated list of ids
* @param className Class to add
*/
export const setClass = function (ids, className) {
ids.split(',').forEach(function (id) {
let rawTask = findTaskById(id)
if (typeof rawTask !== 'undefined') {
rawTask.classes.push(className)
}
})
}
const setClickFun = function (id, functionName, functionArgs) {
if (typeof functionName === 'undefined') {
return
}
let argList = []
if (typeof functionArgs === 'string') {
/* Splits functionArgs by ',', ignoring all ',' in double quoted strings */
argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/)
for (let i = 0; i < argList.length; i++) {
let item = argList[i].trim()
/* Removes all double quotes at the start and end of an argument */
/* This preserves all starting and ending whitespace inside */
if (item.charAt(0) === '"' && item.charAt(item.length - 1) === '"') {
item = item.substr(1, item.length - 2)
}
argList[i] = item
}
}
let rawTask = findTaskById(id)
if (typeof rawTask !== 'undefined') {
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
* @param id The task's id
* @param callbackFunction A function to be executed when clicked on the task or the task's text
*/
2019-03-09 23:17:13 +01:00
const pushFun = function (id, callbackFunction) {
funs.push(function (element) {
const elem = d3.select(element).select(`[id="${id}"]`)
if (elem !== null) {
elem.on('click', function () {
callbackFunction()
})
}
})
funs.push(function (element) {
const elem = d3.select(element).select(`[id="${id}-text"]`)
if (elem !== null) {
elem.on('click', function () {
callbackFunction()
})
}
})
}
/**
* Called by parser when a click definition is found. Registers an event handler.
* @param ids Comma separated list of ids
* @param functionName Function to be called on click
* @param functionArgs Function args the function should be called with
*/
export const setClickEvent = function (ids, functionName, functionArgs) {
ids.split(',').forEach(function (id) {
setClickFun(id, functionName, functionArgs)
})
setClass(ids, 'clickable')
}
/**
* Binds all functions previously added to fun (specified through click) to the element
* @param element
*/
export const bindFunctions = function (element) {
funs.forEach(function (fun) {
fun(element)
})
}
2017-09-10 21:51:48 +08:00
export default {
clear,
setDateFormat,
getDateFormat,
setAxisFormat,
getAxisFormat,
2017-09-10 21:51:48 +08:00
setTitle,
getTitle,
addSection,
getSections,
2017-09-10 21:51:48 +08:00
getTasks,
addTask,
findTaskById,
2019-02-06 16:54:09 -02:00
addTaskOrg,
2019-06-09 12:40:37 -07:00
setExcludes,
getExcludes,
setClickEvent,
setLink,
bindFunctions
2017-09-10 21:51:48 +08:00
}
function getTaskTags (data, task, tags) {
let matchFound = true
while (matchFound) {
matchFound = false
tags.forEach(function (t) {
const pattern = '^\\s*' + t + '\\s*$'
const regex = new RegExp(pattern)
if (data[0].match(regex)) {
task[t] = true
data.shift(1)
matchFound = true
}
})
}
}