UNPKG

gantt-source_management

Version:

Gantt, Schedule,

924 lines (851 loc) 30 kB
import GSTC from '../../dist/gstc.wasm.esm.min.js'; import { Plugin as TimelinePointer } from '../../dist/plugins/timeline-pointer.esm.min.js'; import { Plugin as Selection } from '../../dist/plugins/selection.esm.min.js'; import { Plugin as ItemMovement } from '../../dist/plugins/item-movement.esm.min.js'; import { Plugin as ItemResizing } from '../../dist/plugins/item-resizing.esm.min.js'; import { Plugin as CalendarScroll } from '../../dist/plugins/calendar-scroll.esm.min.js'; import { Plugin as HighlightWeekends } from '../../dist/plugins/highlight-weekends.esm.min.js'; import { Plugin as ProgressBar } from '../../dist/plugins/progress-bar.esm.min.js'; import { Plugin as TimeBookmarks } from '../../dist/plugins/time-bookmarks.esm.min.js'; import { Plugin as DependencyLines } from '../../dist/plugins/dependency-lines.esm.min.js'; import { Plugin as ExportImage } from '../../dist/plugins/export-image.esm.min.js'; import { Plugin as ExportPDF } from '../../dist/plugins/export-pdf.esm.min.js'; globalThis.GSTC = GSTC; const iterations = 100; const GSTCID = GSTC.api.GSTCID; const addDays = 30; function getRandomFaceImage() { return `./faces/face-${Math.ceil(Math.random() * 50)}.jpg`; } const colors = ['#E74C3C', '#DA3C78', '#7E349D', '#0077C0', '#07ABA0', '#0EAC51', '#F1892D']; function getRandomColor() { return colors[Math.floor(Math.random() * colors.length)]; } const startDate = GSTC.api.date('2020-02-01'); const startTime = startDate.valueOf(); const endDate = GSTC.api.date('2020-03-31').endOf('day'); function getInitialRows() { /** * @type {import("../../dist/gstc").Rows} */ const rows = {}; for (let i = 0; i < iterations; i++) { const withParent = i > 0 && i % 2 === 0; const id = GSTCID(String(i)); rows[id] = { id, label: `John Doe ${i}`, parentId: withParent ? GSTCID(String(i - 1)) : undefined, expanded: false, vacations: [], img: getRandomFaceImage(), progress: Math.floor(Math.random() * 100), visible: true, }; } rows[GSTCID('11')].label = 'NESTED TREE HERE'; rows[GSTCID('12')].parentId = GSTCID('11'); rows[GSTCID('13')].parentId = GSTCID('12'); rows[GSTCID('14')].parentId = GSTCID('13'); rows[GSTCID('3')].vacations = [ { from: startDate.add(5, 'days').startOf('day').valueOf(), to: startDate.add(5, 'days').endOf('day').valueOf() }, { from: startDate.add(6, 'days').startOf('day').valueOf(), to: startDate.add(6, 'days').endOf('day').valueOf() }, ]; rows[GSTCID('7')].birthday = [ { from: startDate.add(3, 'day').startOf('day').valueOf(), to: startDate.add(3, 'day').endOf('day').valueOf(), }, ]; return rows; } function generateItemsForDaysView() { /** * @type {import("../../dist/gstc").Items} */ const items = {}; for (let i = 0; i < iterations; i++) { let rowId = GSTCID(i.toString()); let id = GSTCID(i.toString()); let startDayjs = GSTC.api .date(startTime) .startOf('day') .add(Math.floor(Math.random() * addDays), 'day'); let end = startDayjs .clone() .add(Math.floor(Math.random() * 20) + 4, 'day') .endOf('day') .valueOf(); if (end > endDate.valueOf()) end = endDate.valueOf(); items[id] = { id, label: `John Doe ${i}`, progress: Math.round(Math.random() * 100), style: { background: getRandomColor() }, time: { start: startDayjs.startOf('day').valueOf(), end, }, rowId, img: getRandomFaceImage(), classNames: ['additional-custom-class'], description: 'Lorem ipsum dolor sit amet', }; } items[GSTCID('0')].linkedWith = [GSTCID('1')]; items[GSTCID('0')].label = 'Task 0 linked with 1'; items[GSTCID('0')].type = 'task'; items[GSTCID('1')].label = 'Task 1 linked with 0'; items[GSTCID('1')].type = 'task'; items[GSTCID('1')].time = { ...items[GSTCID('0')].time }; items[GSTCID('0')].style = { background: colors[3] }; items[GSTCID('1')].style = { background: colors[3] }; items[GSTCID('3')].dependant = [GSTCID('5')]; items[GSTCID('3')].label = 'Grab and move me into vacation area'; items[GSTCID('3')].time.start = GSTC.api.date(startTime).add(4, 'day').startOf('day').add(5, 'day').valueOf(); items[GSTCID('3')].time.end = GSTC.api.date(items[GSTCID('3')].time.start).endOf('day').add(5, 'day').valueOf(); items[GSTCID('5')].time.start = GSTC.api.date(items[GSTCID('3')].time.end).startOf('day').add(5, 'day').valueOf(); items[GSTCID('5')].time.end = GSTC.api.date(items[GSTCID('5')].time.start).endOf('day').add(2, 'day').valueOf(); items[GSTCID('5')].dependant = [GSTCID('7'), GSTCID('9')]; items[GSTCID('7')].time.start = GSTC.api.date(items[GSTCID('5')].time.end).startOf('day').add(3, 'day').valueOf(); items[GSTCID('7')].time.end = GSTC.api.date(items[GSTCID('7')].time.start).endOf('day').add(2, 'day').valueOf(); items[GSTCID('9')].time.start = GSTC.api.date(items[GSTCID('5')].time.end).startOf('day').add(2, 'day').valueOf(); items[GSTCID('9')].time.end = GSTC.api.date(items[GSTCID('9')].time.start).endOf('day').add(3, 'day').valueOf(); return items; } const columns = { data: { [GSTCID('id')]: { id: GSTCID('id'), data: ({ row }) => GSTC.api.sourceID(row.id), width: 80, sortable: ({ row }) => Number(GSTC.api.sourceID(row.id)), header: { content: 'ID', }, }, [GSTCID('label')]: { id: GSTCID('label'), data: 'label', sortable: 'label', expander: true, isHTML: false, width: 315, header: { content: 'Label', }, }, [GSTCID('progress')]: { id: GSTCID('progress'), data({ row, vido }) { return vido.html`<div style="text-align:center">${row.progress}</div>`; }, width: 100, sortable: 'progress', header: { content: 'Progress', }, }, }, }; /** * @type {import("../../dist/plugins/time-bookmarks").Bookmarks} */ const bookmarks = {}; for (let i = 0; i < 3; i++) { const id = `Bookmark ${i}`; bookmarks[id] = { time: startDate .add(Math.round(Math.random() * addDays), 'day') .startOf('day') .valueOf(), label: id, style: { background: getRandomColor(), }, }; } function itemSlot(vido, props) { const { html, onChange, update } = vido; let imageSrc = ''; let description = ''; onChange((newProps) => { props = newProps; if (!props || !props.item) return; imageSrc = props.item.img; description = props.item.description; update(); }); return (content) => html`<div class="item-image" style="background:url(${imageSrc}),transparent;flex-shrink:0;border-radius:100%;width:34px;height:34px;vertical-align: middle;background-size: 100%;margin: 4px 11px 0px 0px;" ></div> <div class="item-text"> <div class="item-label">${content}</div> <div class="item-description" style="font-size:11px;margin-top:2px;color:#fffffff0;line-height:1em;"> ${description} </div> </div>`; } function rowSlot(vido, props) { const { html, onChange, update, api } = vido; let img = ''; onChange((newProps) => { props = newProps; if (!props || !props.row) return; img = props.row.img; update(); }); return (content) => { if (!props || !props.column) return content; return api.sourceID(props.column.id) === 'label' ? html`<div class="row-content-wrapper" style="display:flex"> <div class="row-content" style="flex-grow:1;">${content}</div> <div class="row-image" style="background:url(${img}),transparent;border-radius:100%;width:34px;height:34px;vertical-align: middle;background-size: 100%;margin: auto 10px;flex-shrink:0;" ></div> </div>` : content; }; } let snapTime = true; function snapStart({ startTime, vido }) { if (!snapTime) return startTime; const date = vido.api.time.findOrCreateMainDateAtTime(startTime.valueOf()); return date.leftGlobalDate; } function snapEnd({ endTime, vido }) { if (!snapTime) return endTime; const date = vido.api.time.findOrCreateMainDateAtTime(endTime.valueOf()); return date.rightGlobalDate; } function canMove(item) { const row = gstc.api.getRow(item.rowId); if (row.vacations) { for (const vacation of row.vacations) { const vacationStart = vacation.from; const vacationEnd = vacation.to; // item start time inside vacation if (item.time.start >= vacationStart && item.time.start <= vacationEnd) { return false; } // item end time inside vacation if (item.time.end >= vacationStart && item.time.end <= vacationEnd) { return false; } // vacation is between item start and end if (item.time.start <= vacationStart && item.time.end >= vacationEnd) { return false; } // item start and end time is inside vacation if (item.time.start >= vacationStart && item.time.end <= vacationEnd) { return false; } } } return true; } /** * @type {import('../../dist/plugins/item-movement').Options} */ const itemMovementOptions = { threshold: { horizontal: 25, vertical: 25, }, snapToTime: { start: snapStart, end({ endTime }) { return endTime; }, }, events: { onMove({ items }) { for (let i = 0, len = items.after.length; i < len; i++) { const item = items.after[i]; if (!canMove(item)) return items.before; } return items.after; }, }, }; /** * @type {import('../../dist/plugins/item-resizing').Options} */ const itemResizeOptions = { threshold: 25, snapToTime: { start: snapStart, end: snapEnd, }, events: { onResize({ items }) { for (const item of items.after) { if (!canMove(item)) return items.before; } return items.after; }, }, }; let hideWeekends = false; function onLevelDates({ dates, level, format, time }) { if (time.period !== 'day') return dates; if (format.period !== time.period) return dates; if (!hideWeekends) return dates; return dates.filter((date) => date.leftGlobalDate.day() !== 0 && date.leftGlobalDate.day() !== 6); } function onItemClick(ev) { const itemElement = ev.target.closest('.gstc__chart-timeline-items-row-item'); const itemId = itemElement.dataset.gstcid; const item = gstc.api.getItem(itemId); console.log('Item click from template', item); } // Typescript usage: // import { Template } from 'gantt-schedule-timeline-calendar'; // const chartTimelineItemsRowItemTemplate: Template = function chartTimelineItemsRowItemTemplate(...); /** * @type {import("../../dist/gstc").Template} // or {import("gantt-schedule-timeline-calendar").Template} */ function chartTimelineItemsRowItemTemplate({ className, labelClassName, styleMap, cache, shouldDetach, cutterLeft, cutterRight, getContent, actions, slots, html, vido, props, }) { const detach = shouldDetach || !props || !props.item; return cache( detach ? null : slots.html( 'outer', html` <div class=${className} data-gstcid=${props.item.id} data-actions=${actions()} style=${styleMap.directive()} @click=${onItemClick} > ${slots.html( 'inner', html` ${cutterLeft()} <div class=${labelClassName}>${slots.html('content', getContent())}</div> ${cutterRight()} ` )} </div> ` ) ); } function myItemSlot(vido, props) { const { onChange } = vido; function onClick() { console.log('Item click from slot', props.item); } onChange((changedProps) => { // if current element is reused to display other item data just update your data so when you click you will display right alert props = changedProps; }); // return render function return (content) => vido.html`<div class="my-item-wrapper" @click=${onClick} style="width:100%;display:flex;overflow:hidden;pointer-events:none;">${content}</div>`; } function onCellCreateVacation({ time, row, vido, content }) { if (!row.vacations) return content; let isVacation = false; for (const vacation of row.vacations) { if (time.leftGlobal >= vacation.from && time.rightGlobal <= vacation.to) { isVacation = true; break; } } if (isVacation) { return vido.html`<div title="🏖️ VACATION" style="height:100%;width:100%;background:#A0A0A010;"></div>${content}`; } return content; } function myVacationRowSlot(vido, props) { const { onChange, html, update, api, state } = vido; let vacationContent = []; onChange((changedProps) => { props = changedProps; if (!props || !props.row || !props.row.vacations) { vacationContent = []; return update(); } const configTime = state.get('config.chart.time'); vacationContent = []; for (const vacation of props.row.vacations) { if (vacation.to < configTime.leftGlobal || vacation.from > configTime.rightGlobal) continue; // birthday date is out of the current view const leftPx = api.time.getViewOffsetPxFromDates(api.time.date(vacation.from)); const rightPx = api.time.getViewOffsetPxFromDates(api.time.date(vacation.to)); const widthPx = rightPx - leftPx - 1; if (widthPx < 0) continue; let textAlign = 'left'; if (widthPx <= 100) textAlign = 'center'; vacationContent.push( html`<div style="position:absolute;left:${leftPx}px;width:${widthPx}px;height:14px;white-space: nowrap;text-overflow:ellipsis;overflow:hidden;font-size:11px;background:#A0A0A0;color:white;text-align:${textAlign};" > Vacation </div>` ); } update(); }); return (content) => html`${vacationContent}${content}`; } function onCellCreateBirthday({ time, row, vido, content }) { if (!row.birthday) return content; let isBirthday = false; for (const birthday of row.birthday) { if (time.leftGlobal >= birthday.from && time.rightGlobal <= birthday.to) { isBirthday = true; break; } } if (isBirthday) { return vido.html`<div title="🎁 BIRTHDAY" style="height:100%;width:100%;font-size:18px;background:#F9B32F10;"></div>${content}`; } return content; } function myBirthdayRowSlot(vido, props) { const { onChange, html, update, api, state } = vido; let birthdayContent = []; onChange((changedProps) => { props = changedProps; if (!props || !props.row || !props.row.birthday) { birthdayContent = []; return update(); } const configTime = state.get('config.chart.time'); birthdayContent = []; for (const birthday of props.row.birthday) { if (birthday.to < configTime.leftGlobal || birthday.from > configTime.rightGlobal) continue; // birthday date is out of the current view const leftPx = api.time.getViewOffsetPxFromDates(api.time.date(birthday.from)); const rightPx = api.time.getViewOffsetPxFromDates(api.time.date(birthday.to)); const widthPx = rightPx - leftPx - 1; if (widthPx < 0) continue; let textAlign = 'left'; if (widthPx <= 100) textAlign = 'center'; birthdayContent.push( html`<div style="position:absolute;left:${leftPx}px;width:${widthPx}px;height:14px;white-space: nowrap;text-overflow:ellipsis;overflow:hidden;font-size:11px;background:#F9B32F;color:white;text-align:${textAlign};" > 🎁 Birthday </div>` ); } update(); }); return (content) => html`${birthdayContent}${content}`; } // Typescript usage: // import { Config } from 'gantt-schedule-timeline-calendar'; // const config: Config = {...}; /** * @type {import("../../dist/gstc").Config} // or {import("gantt-schedule-timeline-calendar").Config} */ const config = { licenseKey: '====BEGIN LICENSE KEY====\nXOfH/lnVASM6et4Co473t9jPIvhmQ/l0X3Ewog30VudX6GVkOB0n3oDx42NtADJ8HjYrhfXKSNu5EMRb5KzCLvMt/pu7xugjbvpyI1glE7Ha6E5VZwRpb4AC8T1KBF67FKAgaI7YFeOtPFROSCKrW5la38jbE5fo+q2N6wAfEti8la2ie6/7U2V+SdJPqkm/mLY/JBHdvDHoUduwe4zgqBUYLTNUgX6aKdlhpZPuHfj2SMeB/tcTJfH48rN1mgGkNkAT9ovROwI7ReLrdlHrHmJ1UwZZnAfxAC3ftIjgTEHsd/f+JrjW6t+kL6Ef1tT1eQ2DPFLJlhluTD91AsZMUg==||U2FsdGVkX1/SWWqU9YmxtM0T6Nm5mClKwqTaoF9wgZd9rNw2xs4hnY8Ilv8DZtFyNt92xym3eB6WA605N5llLm0D68EQtU9ci1rTEDopZ1ODzcqtTVSoFEloNPFSfW6LTIC9+2LSVBeeHXoLEQiLYHWihHu10Xll3KsH9iBObDACDm1PT7IV4uWvNpNeuKJc\npY3C5SG+3sHRX1aeMnHlKLhaIsOdw2IexjvMqocVpfRpX4wnsabNA0VJ3k95zUPS3vTtSegeDhwbl6j+/FZcGk9i+gAy6LuetlKuARjPYn2LH5Be3Ah+ggSBPlxf3JW9rtWNdUoFByHTcFlhzlU9HnpnBUrgcVMhCQ7SAjN9h2NMGmCr10Rn4OE0WtelNqYVig7KmENaPvFT+k2I0cYZ4KWwxxsQNKbjEAxJxrzK4HkaczCvyQbzj4Ppxx/0q+Cns44OeyWcwYD/vSaJm4Kptwpr+L4y5BoSO/WeqhSUQQ85nvOhtE0pSH/ZXYo3pqjPdQRfNm6NFeBl2lwTmZUEuw==\n====END LICENSE KEY====', innerHeight: 700, //autoInnerHeight: true, plugins: [ HighlightWeekends(), TimelinePointer(), // timeline pointer must go first before selection, resizing and movement Selection({ events: { onEnd(selected) { console.log('Selected', selected); return selected; }, }, }), ItemResizing(itemResizeOptions), // resizing must fo before movement ItemMovement(itemMovementOptions), CalendarScroll(), ProgressBar(), TimeBookmarks({ bookmarks, }), DependencyLines({ onLine: [ (line) => { line.type = GSTC.api.sourceID(line.fromItem.id) === '3' ? 'smooth' : 'square'; return line; }, ], }), ExportImage(), ExportPDF(), ], list: { row: { height: 68, }, rows: getInitialRows(), columns, }, chart: { time: { from: startDate.valueOf(), to: endDate.valueOf(), onLevelDates: [onLevelDates], }, item: { height: 50, gap: { top: 14, //bottom: 0, }, }, items: generateItemsForDaysView(), grid: { cell: { onCreate: [onCellCreateVacation, onCellCreateBirthday], }, }, }, scroll: { vertical: { precise: true, byPixels: true }, horizontal: { precise: true, byPixels: true }, }, slots: { 'chart-timeline-items-row-item': { content: [itemSlot], inner: [myItemSlot] }, 'list-column-row': { content: [rowSlot] }, 'chart-timeline-grid-row': { content: [myBirthdayRowSlot, myVacationRowSlot] }, }, templates: { 'chart-timeline-items-row-item': chartTimelineItemsRowItemTemplate, }, //utcMode: true, }; let gstc; let state; function mountGSTC() { state = GSTC.api.stateFromConfig(config); const element = document.createElement('div'); element.id = 'gstc'; document.querySelector('#toolbox')?.after(element); gstc = GSTC({ element, state, }); //@ts-ignore globalThis.state = state; //@ts-ignore globalThis.gstc = gstc; } mountGSTC(); // TOOLBOX BUTTONS // Select first two cells function selectCells() { const api = gstc.api; const allCells = api.getGridCells(); api.plugins.Selection.selectCells([allCells[0].id, allCells[1].id]); console.log(api.plugins.Selection.getSelection()); } // scroll to first item function scrollToFirstItem() { const api = gstc.api; const firstItem = gstc.state.get(`config.chart.items.${api.GSTCID('1')}`); api.scrollToTime(firstItem.time.start, false); } globalThis.scrollToFirstItem = scrollToFirstItem; function downloadImage() { gstc.api.plugins.ExportImage.download(); } async function downloadPdf() { await gstc.api.plugins.ExportPDF.download('timeline.pdf'); console.log('PDF downloaded'); } async function takeShotPdf() { const img = await gstc.api.plugins.ExportPDF.takeShot(); console.log('PDF shot taken', img); alert( `\nScreenshot taken\n\nYou can add more screenshots and then download them all together with "Get screenshots" button.` ); } async function getPdf() { await gstc.api.plugins.ExportPDF.getPDF('timeline.pdf'); await gstc.api.plugins.ExportPDF.clearPDF(); // don't forget to clear pdf after you are done with it console.log('PDF downloaded'); } function downloadPdfFull() { gstc.api.plugins.ExportPDF.downloadFull('timeline.pdf'); } let darkModeEnabled = false; function toggleDarkMode(ev) { darkModeEnabled = ev.target.checked; const el = document.getElementById('gstc'); if (darkModeEnabled) { el?.classList.add('gstc--dark'); document.body.classList.add('gstc--dark'); } else { el?.classList.remove('gstc--dark'); document.body.classList.remove('gstc--dark'); } } function toggleHideWeekends(ev) { hideWeekends = ev.target.checked; gstc.api.time.recalculateTime(); } function toggleSnapTime(ev) { snapTime = ev.target.checked; } function toggleExpandTime(ev) { const expandTime = ev.target.checked; const moveOutEl = document.getElementById('move-out'); if (moveOutEl && expandTime) { // @ts-ignore moveOutEl.checked = expandTime; toggleMoveOut({ target: moveOutEl }); } state.update('config.chart.time.autoExpandTimeFromItems', expandTime); } function toggleMoveOut(ev) { const moveOut = ev.target.checked; const expandTimeEl = document.getElementById('expand-time'); if (expandTimeEl && !moveOut) { // @ts-ignore expandTimeEl.checked = moveOut; toggleExpandTime({ target: expandTimeEl }); } state.update('config.plugin.ItemMovement.allowItemsToGoOutsideTheArea', moveOut); state.update('config.plugin.ItemResizing.allowItemsToGoOutsideTheArea', moveOut); } function zoomChangeSelect(ev) { const period = ev.target.value; let zoom = 20; let from = gstc.api.time.date('2020-02-01').startOf('day').valueOf(); let to = gstc.api.time.date('2020-03-01').endOf('month').valueOf(); switch (period) { case 'hours': zoom = 16; from = gstc.api.time.date('2020-02-01').startOf('day').valueOf(); to = gstc.api.time.date('2020-03-01').endOf('month').valueOf(); break; case 'days': zoom = 20; from = gstc.api.time.date('2020-02-01').startOf('day').valueOf(); to = gstc.api.time.date('2020-03-01').endOf('month').valueOf(); break; case 'weeks': zoom = 23; from = gstc.api.time.date('2020-02-01').startOf('day').valueOf(); to = gstc.api.time.date('2020-08-01').endOf('month').valueOf(); break; case 'months': zoom = 26; from = gstc.api.time.date('2020-01-01').startOf('day').valueOf(); to = gstc.api.time.date('2024-01-10').endOf('year').valueOf(); break; } state.update('config.chart.time', (time) => { time.zoom = zoom; time.from = from; time.to = to; return time; }); const zoomRange = document.getElementById('zoom-range'); //@ts-ignore if (zoomRange) zoomRange.value = zoom; } function zoomChangeRange(ev) { const zoom = Number(ev.target.value); let period = 'days'; let from = gstc.api.time.date('2020-02-01').startOf('day'); let to = gstc.api.time.date('2020-03-01').endOf('month'); if (zoom >= 16) { period = 'hours'; from = gstc.api.time.date('2020-02-01').startOf('day'); to = gstc.api.time.date('2020-03-01').endOf('month'); } if (zoom >= 20) { period = 'days'; from = gstc.api.time.date('2020-02-01').startOf('day'); to = gstc.api.time.date('2020-03-01').endOf('month'); } if (zoom >= 20) { period = 'weeks'; from = gstc.api.time.date('2020-02-01').startOf('day'); to = gstc.api.time.date('2020-08-01').endOf('month'); } if (zoom >= 23) { period = 'months'; from = gstc.api.time.date('2020-01-01').startOf('day'); to = gstc.api.time.date('2024-01-10').endOf('year'); } gstc.state.update('config.chart.time', (time) => { time.zoom = zoom; time.from = from.valueOf(); time.to = to.valueOf(); return time; }); const zoomSelect = document.getElementById('zoom'); // @ts-ignore if (zoomSelect) zoomSelect.value = period; } function searchRows(event) { const copiedRows = getInitialRows(); const search = String(event.target.value).trim(); console.log('search', search); const regex = new RegExp(`[\s\S]?${search}[\s\S]?`, 'gi'); const rowsToKeep = []; for (const rowId in copiedRows) { const row = copiedRows[rowId]; const rowData = gstc.api.getRowData(rowId); if (regex.test(row.label)) { rowsToKeep.push(rowId); for (const childRowId of rowData.allChildren) { rowsToKeep.push(childRowId); } for (const parentRowId of rowData.parents) { rowsToKeep.push(parentRowId); if (search) copiedRows[parentRowId].expanded = true; } } regex.lastIndex = 0; } const uniqueRowsToKeep = [...new Set(rowsToKeep)]; // js way to get only unique row id's- we don't want duplicates here for (const rowId in copiedRows) { if (uniqueRowsToKeep.includes(rowId)) { copiedRows[rowId].visible = true; } else { copiedRows[rowId].visible = false; } } state.update('config.list.rows', (currentRows) => { return copiedRows; }); } const historyStates = []; globalThis.historyStates = historyStates; let currentStateName = ''; function saveCurrentState(stateName) { const items = GSTC.api.merge({}, state.get('config.chart.items')); historyStates.push({ name: stateName, state: items }); currentStateName = stateName; updateToolBox(); } function restoreState(stateName) { const historyState = historyStates.find((s) => s.name == stateName); currentStateName = stateName; const clonedState = GSTC.api.merge({}, historyState.state); state.update('config.chart.items', clonedState); updateToolBox(); } function onRestoreStateChange(ev) { const name = ev.target.value; restoreState(name); } function openSaveCurrentStateDialog() { const stateName = prompt('Enter current state name'); saveCurrentState(stateName); } function deleteSelectedItems() { const selectedItems = gstc.api.plugins.Selection.getSelected()['chart-timeline-items-row-item']; // cloned items gstc.api.plugins.Selection.selectItems([]); // clear selection state.update('config.plugin.Selection.lastSelecting.chart-timeline-items-row-item', []); state.update('config.chart.items', (items) => { for (const item of selectedItems) { delete items[item.id]; } return items; }); } const html = GSTC.lithtml.html; function updateToolBox() { const searchBoxHTML = html`<input type="text" @input=${searchRows} placeholder="Search" />`; const historyStateHTML = html`<button @click="${openSaveCurrentStateDialog}">Save items</button> <label>Restore items:</label> <select @change=${onRestoreStateChange}> ${historyStates.map( (historyState) => html`<option value=${historyState.name} ?selected=${historyState.name === currentStateName}> ${historyState.name} </option>` )} </select>`; const toolboxButtons = html` <div class="toolbox-row"> <div class="toolbox-item"><button @click=${selectCells}>Select first cells</button></div> <div class="toolbox-item"><button @click=${scrollToFirstItem}>Scroll to first item</button></div> <div class="toolbox-item"><button @click=${downloadImage}>Download image</button></div> <div class="toolbox-item"><button @click=${downloadPdf}>PDF (current view)</button></div> <div class="toolbox-item"><button @click=${downloadPdfFull}>PDF (full)</button></div> <div class="toolbox-item"><button @click=${takeShotPdf}>Take screenshot</button></div> <div class="toolbox-item">-></div> <div class="toolbox-item"><button @click=${getPdf}>Get screenshots</button></div> <div class="toolbox-item">${historyStateHTML}</div> <div class="toolbox-item"> <label>Zoom:</label> <select @change="${zoomChangeSelect}" id="zoom"> <option value="hours">Hours</option> <option value="days" selected>Days</option> <option value="weeks">Weeks</option> <option value="months">Months</option> </select> </div> <div class="toolbox-item"> <button @click=${deleteSelectedItems}>Delete selected items</button> </div> </div> <div class="toolbox-row"> <div class="toolbox-item">${searchBoxHTML}</div> <div class="toolbox-item"> <input type="checkbox" id="dark-mode" @change=${toggleDarkMode} /> <label for="dark-mode">Dark mode</label> </div> <div class="toolbox-item"> <input type="checkbox" id="snap-time" @change=${toggleSnapTime} checked /> <label for="snap-time">Snap time (item movement)</label> </div> <div class="toolbox-item"> <input type="checkbox" id="hide-weekends" @change=${toggleHideWeekends} /> <label for="hide-weekends">Hide weekends</label> </div> <div class="toolbox-item"> <input type="checkbox" id="expand-time" @change=${toggleExpandTime} /> <label for="expand-time">Expand view when item is outside</label> </div> <div class="toolbox-item"> <input type="checkbox" id="move-out" @change=${toggleMoveOut} checked /> <label for="move-out">Alow items to move outside area</label> </div> <div class="toolbox-item"> <label for="zoom">Zoom:</label> <input id="zoom-range" type="range" min="16" max="26" value="20" step="0.1" @change=${zoomChangeRange} style="width:200px" /> </div> </div> </div>`; // @ts-ignore GSTC.lithtml.render(toolboxButtons, document.getElementById('toolbox')); } updateToolBox(); const gstcEl = document.getElementById('gstc'); gstcEl?.addEventListener('gstc-loaded', () => { console.log('GSTC loaded!'); saveCurrentState('Initial'); });