Integrations And Frameworks
Live example
Section titled “Live example”Save and load a project through the labeled in-memory transport, or export JSON and import the file again. This demonstrates the snapshot boundary; REST, GraphQL, SQL, and Supabase source recipes below require your own service/client.
Source code
import { controlAttributes, createControl } from '@revolist/ui';import { defineCustomElements } from '@revolist/revogrid/loader';defineCustomElements();
import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot,} from '@revolist/gantt';import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';import { currentTheme } from '../composables/useRandomData';import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES, SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR,} from './shared/gantt-project-data';import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows,} from './GanttProductionPatternsShared';import './gantt-production-patterns.css';
export function load(parentSelector: string) { const parent = document.querySelector(parentSelector); if (!parent) return () => {}; const root = document.createElement('section'); const controls = document.createElement('div'); const status = document.createElement('p'); const toolbar = document.createElement('div'); const grid = document.createElement('revo-grid'); const persistence = createProductionPersistence(); let readOnly = false; let localized = false; let large = false; let project: ProjectSnapshot | null = null; const applyConfig = () => { grid.gantt = createProductionGanttConfig(readOnly, localized, project); }; const applyProject = () => { applyConfig(); grid.ganttDependencies = project?.dependencies ?? (large ? [] : SHOWCASE_DEPENDENCIES); grid.ganttCalendars = project?.calendars ?? [STANDARD_CALENDAR]; grid.ganttResources = project?.resources ?? SHOWCASE_RESOURCES; grid.ganttAssignments = project?.assignments ?? (large ? [] : SHOWCASE_ASSIGNMENTS); grid.ganttBaselines = project?.baselines ?? (large ? [] : SHOWCASE_BASELINES); grid.source = project ? productionSourceRows(project) : createProductionTasks(large); }; const toggle = (label: string, update: (value: boolean) => void) => { const element = document.createElement('label'); const input = createControl(document, 'input', { type: 'checkbox' }); input.type = 'checkbox'; input.addEventListener('change', () => update(input.checked)); element.append(input, document.createTextNode(label)); return { element, input }; }; const run = async (action: () => unknown | Promise<unknown>) => { try { await action(); } catch (error) { status.textContent = productionError(error); } }; const button = (label: string, action: () => unknown | Promise<unknown>) => { const element = createControl(document, 'button', { type: 'button' }); element.type = 'button'; element.textContent = label; element.className = controlAttributes('button', { class: 'rv-btn' }).class; element.addEventListener('click', () => { void run(action); }); return element; }; root.className = 'gantt-production-demo'; controls.className = 'gantt-production-controls'; status.className = 'gantt-production-status'; status.setAttribute('role', 'status'); toolbar.className = 'gantt-production-toolbar'; grid.className = 'gantt-production-grid'; grid.theme = currentTheme().isDark() ? 'darkCompact' : 'compact'; grid.hideAttribution = true; grid.plugins = [ GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin, ]; grid.rowSelect = { rowOrder: true }; grid.columns = [...SHOWCASE_COLUMNS_WITH_ROW_SELECT]; const largeToggle = toggle('750 tasks', (value) => { large = value; project = null; applyProject(); }); const restore = button('Restore', async () => { project = await persistence.load(); large = false; largeToggle.input.checked = false; applyProject(); status.textContent = 'Restored the saved project, including tasks and project collections.'; }); restore.disabled = true; const fileInput = createControl(document, 'input', { type: 'file' }); fileInput.type = 'file'; fileInput.hidden = true; fileInput.accept = '.json,application/json'; fileInput.setAttribute('aria-label', 'Import JSON file'); fileInput.addEventListener('change', async () => { const file = fileInput.files?.[0]; if (file) await run(async () => { project = await persistence.importFile(file); large = false; largeToggle.input.checked = false; applyProject(); status.textContent = `Imported ${project.tasks.length} tasks from ${file.name}.`; }); fileInput.value = ''; }); const options = document.createElement('div'); options.className = 'gantt-production-options'; const actions = document.createElement('div'); actions.className = 'gantt-production-actions'; options.append( toggle('Read only', (value) => { readOnly = value; applyConfig(); }).element, toggle('Español dates', (value) => { localized = value; applyConfig(); }).element, largeToggle.element, ); actions.append( fileInput, button('Import JSON', () => fileInput.click()), button('JSON', () => persistence.exportFile(grid)), button('Save', async () => { await persistence.save(grid); restore.disabled = false; status.textContent = 'Saved the current edited project in browser memory.'; }), restore, button('Print', () => persistence.print(grid)), ); controls.append(options, actions); root.append(controls, status, toolbar, grid); parent.appendChild(root); applyProject(); defineGanttToolbar(toolbar, { grid, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true }, }); return () => { toolbar.textContent = ''; grid.remove(); root.remove(); };}<template> <section class="gantt-production-demo"> <div class="gantt-production-controls"> <div class="gantt-production-options"> <label ><input v-rv-control v-model="readOnly" type="checkbox" />Read only</label > <label ><input v-rv-control v-model="localized" type="checkbox" />Español dates</label > <label ><input v-rv-control v-model="large" type="checkbox" @change="project = null" />750 tasks</label > </div> <div class="gantt-production-actions"> <input v-rv-control ref="importInput" type="file" hidden accept=".json,application/json" aria-label="Import JSON file" @change="importJson" /> <button v-rv-control type="button" class="rv-btn" @click="importInput?.click()" > Import JSON </button> <button v-rv-control type="button" class="rv-btn" @click="run(() => persistence.exportFile(getGrid()))" > JSON </button> <button v-rv-control type="button" class="rv-btn" @click="save"> Save </button> <button v-rv-control type="button" class="rv-btn" :disabled="!hasSaved" @click="restore" > Restore </button> <button v-rv-control type="button" class="rv-btn" @click="run(() => persistence.print(getGrid()))" > Print </button> </div> </div> <p class="gantt-production-status" role="status">{{ status }}</p> <div ref="toolbarRef" class="gantt-production-toolbar"></div> <RevoGrid ref="gridRef" class="gantt-production-grid" hide-attribution :theme="isDark ? 'darkCompact' : 'compact'" :plugins="plugins" :row-select.prop="rowSelect" :source="tasks" :columns="columns" :gantt.prop="ganttConfig" :gantt-dependencies.prop="dependencies" :gantt-calendars.prop="calendars" :gantt-resources.prop="resources" :gantt-assignments.prop="assignments" :gantt-baselines.prop="baselines" /> </section></template>
<script setup lang="ts">import { vRvControl } from '@revolist/ui/vue-control';import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';import RevoGrid from '@revolist/vue3-datagrid';import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot,} from '@revolist/gantt';import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';import { currentThemeVue } from '../composables/useRandomData';import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES, SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR,} from './shared/gantt-project-data';import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows,} from './GanttProductionPatternsShared';import './gantt-production-patterns.css';const { isDark } = currentThemeVue();const plugins = [ GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin,];const rowSelect = { rowOrder: true };const columns = SHOWCASE_COLUMNS_WITH_ROW_SELECT;const readOnly = ref(false);const localized = ref(false);const large = ref(false);const project = ref<ProjectSnapshot | null>(null);const status = ref('');const hasSaved = ref(false);const persistence = createProductionPersistence();const tasks = computed(() => project.value ? productionSourceRows(project.value) : createProductionTasks(large.value),);const dependencies = computed( () => project.value?.dependencies ?? (large.value ? [] : SHOWCASE_DEPENDENCIES),);const calendars = computed( () => project.value?.calendars ?? [STANDARD_CALENDAR],);const resources = computed( () => project.value?.resources ?? SHOWCASE_RESOURCES,);const assignments = computed( () => project.value?.assignments ?? (large.value ? [] : SHOWCASE_ASSIGNMENTS),);const baselines = computed( () => project.value?.baselines ?? (large.value ? [] : SHOWCASE_BASELINES),);const ganttConfig = computed(() => createProductionGanttConfig(readOnly.value, localized.value, project.value),);const gridRef = ref<InstanceType<typeof RevoGrid> | HTMLRevoGridElement | null>( null,);const importInput = ref<HTMLInputElement | null>(null);const toolbarRef = ref<HTMLElement | null>(null);let frame = 0;function getGrid(): HTMLRevoGridElement { const value = gridRef.value as | (InstanceType<typeof RevoGrid> & { $el?: HTMLRevoGridElement }) | HTMLRevoGridElement | null; const grid = value && '$el' in value ? value.$el : value; if (!(grid instanceof HTMLElement)) throw new Error('The grid is still loading.'); return grid as HTMLRevoGridElement;}async function run(action: () => unknown | Promise<unknown>) { try { await action(); } catch (error) { status.value = productionError(error); }}async function save() { await run(async () => { await persistence.save(getGrid()); hasSaved.value = true; status.value = 'Saved the current edited project in browser memory.'; });}async function restore() { await run(async () => { project.value = await persistence.load(); large.value = false; status.value = 'Restored the saved project, including tasks and project collections.'; });}async function importJson(event: Event) { const input = event.target as HTMLInputElement; const file = input.files?.[0]; if (file) await run(async () => { project.value = await persistence.importFile(file); large.value = false; status.value = `Imported ${project.value.tasks.length} tasks from ${file.name}.`; }); input.value = '';}function mountToolbar() { let grid: HTMLRevoGridElement; try { grid = getGrid(); } catch { frame = requestAnimationFrame(mountToolbar); return; } if (toolbarRef.value) defineGanttToolbar(toolbarRef.value, { grid, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true }, });}onMounted(async () => { await nextTick(); mountToolbar();});onBeforeUnmount(() => { cancelAnimationFrame(frame); if (toolbarRef.value) toolbarRef.value.textContent = '';});</script>import { ButtonControl as UiButton, Input as UiInput,} from '@revolist/ui/react-control';import React, { useEffect, useMemo, useRef, useState } from 'react';import { RevoGrid } from '@revolist/react-datagrid';import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot,} from '@revolist/gantt';import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';import { currentTheme } from '../composables/useRandomData';import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES, SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR,} from './shared/gantt-project-data';import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows,} from './GanttProductionPatternsShared';import './gantt-production-patterns.css';
export default function GanttProductionPatterns() { const gridRef = useRef<HTMLRevoGridElement>(null); const importInput = useRef<HTMLInputElement>(null); const toolbarRef = useRef<HTMLDivElement>(null); const [readOnly, setReadOnly] = useState(false); const [localized, setLocalized] = useState(false); const [large, setLarge] = useState(false); const [project, setProject] = useState<ProjectSnapshot | null>(null); const [status, setStatus] = useState(''); const [hasSaved, setHasSaved] = useState(false); const persistence = useMemo(() => createProductionPersistence(), []); const plugins = useMemo( () => [ GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin, ], [], ); const rowSelect = useMemo(() => ({ rowOrder: true }), []); const columns = useMemo(() => SHOWCASE_COLUMNS_WITH_ROW_SELECT, []); const tasks = useMemo( () => project ? productionSourceRows(project) : createProductionTasks(large), [project, large], ); const gantt = useMemo( () => createProductionGanttConfig(readOnly, localized, project), [readOnly, localized, project], ); const dependencies = useMemo( () => project?.dependencies ?? (large ? [] : SHOWCASE_DEPENDENCIES), [project, large], ); const calendars = useMemo( () => project?.calendars ?? [STANDARD_CALENDAR], [project], ); const resources = useMemo( () => project?.resources ?? SHOWCASE_RESOURCES, [project], ); const assignments = useMemo( () => project?.assignments ?? (large ? [] : SHOWCASE_ASSIGNMENTS), [project, large], ); const baselines = useMemo( () => project?.baselines ?? (large ? [] : SHOWCASE_BASELINES), [project, large], ); function getGrid(): HTMLRevoGridElement { if (!gridRef.current) throw new Error('The grid is still loading.'); return gridRef.current; } async function run(action: () => unknown | Promise<unknown>) { try { await action(); } catch (error) { setStatus(productionError(error)); } } async function save() { await run(async () => { await persistence.save(getGrid()); setHasSaved(true); setStatus('Saved the current edited project in browser memory.'); }); } async function restore() { await run(async () => { setProject(await persistence.load()); setLarge(false); setStatus( 'Restored the saved project, including tasks and project collections.', ); }); } async function importJson(event: React.ChangeEvent<HTMLInputElement>) { const input = event.currentTarget; const file = input.files?.[0]; if (file) await run(async () => { const imported = await persistence.importFile(file); setProject(imported); setLarge(false); setStatus(`Imported ${imported.tasks.length} tasks from ${file.name}.`); }); input.value = ''; } useEffect(() => { const toolbar = toolbarRef.current; if (!toolbar || !gridRef.current) return; defineGanttToolbar(toolbar, { grid: gridRef.current, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true }, }); return () => { toolbar.textContent = ''; }; }, []); return ( <section className="gantt-production-demo"> <div className="gantt-production-controls"> <div className="gantt-production-options"> <label> <UiInput type="checkbox" checked={readOnly} onChange={(event) => setReadOnly(event.currentTarget.checked)} /> Read only </label> <label> <UiInput type="checkbox" checked={localized} onChange={(event) => setLocalized(event.currentTarget.checked)} /> Español dates </label> <label> <UiInput type="checkbox" checked={large} onChange={(event) => { setLarge(event.currentTarget.checked); setProject(null); }} /> 750 tasks </label> </div> <div className="gantt-production-actions"> <UiInput ref={importInput} type="file" hidden accept=".json,application/json" aria-label="Import JSON file" onChange={importJson} /> <UiButton type="button" className="rv-btn" onClick={() => importInput.current?.click()} > Import JSON </UiButton> <UiButton type="button" className="rv-btn" onClick={() => { void run(() => persistence.exportFile(getGrid())); }} > JSON </UiButton> <UiButton type="button" className="rv-btn" onClick={save}> Save </UiButton> <UiButton type="button" className="rv-btn" disabled={!hasSaved} onClick={restore} > Restore </UiButton> <UiButton type="button" className="rv-btn" onClick={() => { void run(() => persistence.print(getGrid())); }} > Print </UiButton> </div> </div> <p className="gantt-production-status" role="status"> {status} </p> <div ref={toolbarRef} className="gantt-production-toolbar" /> <RevoGrid ref={gridRef} className="gantt-production-grid" theme={currentTheme().isDark() ? 'darkCompact' : 'compact'} hideAttribution plugins={plugins} rowSelect={rowSelect} source={tasks} columns={columns} gantt={gantt} ganttDependencies={dependencies} ganttCalendars={calendars} ganttResources={resources} ganttAssignments={assignments} ganttBaselines={baselines} /> </section> );}import { UiControlDirective } from '@revolist/ui/angular-control';import { ChangeDetectorRef, inject, AfterViewInit, Component, ElementRef, NO_ERRORS_SCHEMA, OnDestroy, ViewChild, ViewEncapsulation,} from '@angular/core';import { RevoGrid } from '@revolist/angular-datagrid';import { defineGanttToolbar, GanttPlugin, GanttCalendarEditorPlugin, type ProjectSnapshot,} from '@revolist/gantt';import { ExportExcelPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';import { currentTheme } from '../composables/useRandomData';import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_COLUMNS_WITH_ROW_SELECT, SHOWCASE_DEPENDENCIES, SHOWCASE_RESOURCES, SHOWCASE_TOOLBAR_COLUMNS, STANDARD_CALENDAR,} from './shared/gantt-project-data';import { createProductionGanttConfig, createProductionTasks, createProductionPersistence, productionError, productionSourceRows,} from './GanttProductionPatternsShared';
@Component({ selector: 'gantt-production-patterns-grid', standalone: true, schemas: [NO_ERRORS_SCHEMA], imports: [UiControlDirective, RevoGrid], styleUrls: ['./gantt-production-patterns.css'], encapsulation: ViewEncapsulation.None, template: `<section class="gantt-production-demo"> <div class="gantt-production-controls"> <div class="gantt-production-options"> <label ><input rvControl type="checkbox" [checked]="readOnly" (change)="setReadOnly($any($event.target).checked)" />Read only</label > <label ><input rvControl type="checkbox" [checked]="localized" (change)="setLocalized($any($event.target).checked)" />Español dates</label > <label ><input rvControl type="checkbox" [checked]="large" (change)="setLarge($any($event.target).checked)" />750 tasks</label > </div> <div class="gantt-production-actions"> <input rvControl #importFile type="file" hidden accept=".json,application/json" aria-label="Import JSON file" (change)="importJson($event)" /> <button rvControl type="button" class="rv-btn" (click)="importFile.click()" > Import JSON </button> <button rvControl type="button" class="rv-btn" (click)="exportJson()"> JSON </button> <button rvControl type="button" class="rv-btn" (click)="save()"> Save </button> <button rvControl type="button" class="rv-btn" [disabled]="!hasSaved" (click)="restore()" > Restore </button> <button rvControl type="button" class="rv-btn" (click)="print()"> Print </button> </div> </div> <p class="gantt-production-status" role="status">{{ status }}</p> <div #toolbar class="gantt-production-toolbar"></div> <revo-grid #grid class="gantt-production-grid" [theme]="theme" [hideAttribution]="true" [plugins]="plugins" [rowSelect]="rowSelect" [source]="tasks" [columns]="columns" [gantt]="ganttConfig" [ganttDependencies]="dependencies" [ganttCalendars]="calendars" [ganttResources]="resources" [ganttAssignments]="assignments" [ganttBaselines]="baselines" ></revo-grid> </section>`,})export class GanttProductionPatternsGridComponent implements AfterViewInit, OnDestroy{ private readonly changeDetector = inject(ChangeDetectorRef); @ViewChild('grid', { read: ElementRef }) gridRef!: ElementRef<HTMLRevoGridElement>; @ViewChild('toolbar', { read: ElementRef }) toolbarRef!: ElementRef<HTMLElement>; theme = currentTheme().isDark() ? 'darkCompact' : 'compact'; plugins = [ GanttPlugin, GanttCalendarEditorPlugin, ExportExcelPlugin, RowSelectPlugin, ]; rowSelect = { rowOrder: true }; columns = SHOWCASE_COLUMNS_WITH_ROW_SELECT; calendars = [STANDARD_CALENDAR]; resources = [...SHOWCASE_RESOURCES]; assignments = [...SHOWCASE_ASSIGNMENTS]; baselines = [...SHOWCASE_BASELINES]; readOnly = false; localized = false; large = false; tasks = createProductionTasks(); dependencies = SHOWCASE_DEPENDENCIES; project: ProjectSnapshot | null = null; status = ''; hasSaved = false; private persistence = createProductionPersistence(); ganttConfig = createProductionGanttConfig(); private refresh() { this.ganttConfig = createProductionGanttConfig( this.readOnly, this.localized, this.project, ); } private refreshProject() { this.tasks = this.project ? productionSourceRows(this.project) : createProductionTasks(this.large); this.dependencies = [ ...(this.project?.dependencies ?? (this.large ? [] : SHOWCASE_DEPENDENCIES)), ]; this.calendars = [...(this.project?.calendars ?? [STANDARD_CALENDAR])]; this.resources = [...(this.project?.resources ?? SHOWCASE_RESOURCES)]; this.assignments = [ ...(this.project?.assignments ?? (this.large ? [] : SHOWCASE_ASSIGNMENTS)), ]; this.baselines = [ ...(this.project?.baselines ?? (this.large ? [] : SHOWCASE_BASELINES)), ]; this.refresh(); } private getGrid(): HTMLRevoGridElement { const grid = this.gridRef?.nativeElement; if (!grid) throw new Error('The grid is still loading.'); return grid; } private async run(action: () => unknown | Promise<unknown>) { try { await action(); } catch (error) { this.status = productionError(error); } finally { this.changeDetector.markForCheck(); } } setReadOnly(value: boolean) { this.readOnly = value; this.refresh(); } setLocalized(value: boolean) { this.localized = value; this.refresh(); } setLarge(value: boolean) { this.large = value; this.project = null; this.refreshProject(); } exportJson() { return this.run(() => this.persistence.exportFile(this.getGrid())); } print() { return this.run(() => this.persistence.print(this.getGrid())); } save() { return this.run(async () => { await this.persistence.save(this.getGrid()); this.hasSaved = true; this.status = 'Saved the current edited project in browser memory.'; }); } restore() { return this.run(async () => { this.project = await this.persistence.load(); this.large = false; this.refreshProject(); this.status = 'Restored the saved project, including tasks and project collections.'; }); } async importJson(event: Event) { const input = event.target as HTMLInputElement; const file = input.files?.[0]; if (file) await this.run(async () => { this.project = await this.persistence.importFile(file); this.large = false; this.refreshProject(); this.status = `Imported ${this.project.tasks.length} tasks from ${file.name}.`; }); input.value = ''; } ngAfterViewInit() { defineGanttToolbar(this.toolbarRef.nativeElement, { grid: this.gridRef.nativeElement, columns: SHOWCASE_TOOLBAR_COLUMNS, controls: { baseline: true, calendars: true }, }); } ngOnDestroy() { if (this.toolbarRef?.nativeElement) this.toolbarRef.nativeElement.textContent = ''; }}import { exportProjectSnapshot, parseProjectSnapshotJson, type GanttPluginConfig, type GanttTaskSourceRow, type ProjectSnapshot,} from '@revolist/gantt';type ISODateString = import('@revolist/gantt').TaskEntity['startDate'];import { getGanttScheduleSnapshot } from './recipes/current-schedule-snapshot';import { ProjectSnapshotRestAdapter } from './recipes/rest-adapter';import { printGanttContainer } from './recipes/print-pdf-recipe';import { SHOWCASE_ASSIGNMENTS, SHOWCASE_BASELINES, SHOWCASE_DEPENDENCIES, SHOWCASE_GANTT_CONFIG, SHOWCASE_RESOURCES, SHOWCASE_TASKS, STANDARD_CALENDAR,} from './shared/gantt-project-data';
export function createProductionGanttConfig( readOnly = false, localized = false, project?: ProjectSnapshot | null,): GanttPluginConfig { return { ...SHOWCASE_GANTT_CONFIG, ...(project ? { id: project.id, name: project.name, version: project.version, currency: project.currency, timeZone: project.timeZone, primaryCalendarId: project.primaryCalendarId, updatedAt: project.updatedAt, durationSettings: project.durationSettings, calendars: project.calendars, } : {}), allowTaskCreate: true, taskCreateRow: true, readOnly, labels: localized ? { taskMode: { auto: 'Automática', manual: 'Manual' }, workflowStatus: { 'not-started': 'Sin iniciar', 'in-progress': 'En curso', done: 'Finalizada', blocked: 'Bloqueada', }, } : undefined, dateFormats: localized ? { locale: 'es-ES', table: { dateStyle: 'medium' }, tooltip: { dateStyle: 'long' }, } : undefined, visuals: { ...SHOWCASE_GANTT_CONFIG.visuals, showBaseline: true, showTaskLabels: true, timeRanges: [ { id: 'release-window', startDate: '2026-02-16', endDate: '2026-02-20', label: 'Release window', color: '#8b5cf633', }, ], milestoneLines: [ { id: 'go-live', date: '2026-02-27', label: 'Go live', color: '#e11d48', }, ], taskMarkerHook: (task) => task.deadlineDate ? [ { id: `deadline-${task.id}`, date: task.deadlineDate, label: 'Deadline', }, ] : [], taskBarColorHook: ({ row }) => row.id === SHOWCASE_TASKS[1].id ? { barColor: '#64748b', className: 'gantt-production-locked' } : undefined, taskTooltipFields: [ 'startDate', 'endDate', 'duration', 'percentDone', 'assignees', ], }, } as GanttPluginConfig;}
export function createProductionTasks(large = false): GanttTaskSourceRow[] { if (!large) { return SHOWCASE_TASKS.map((task, index) => ({ ...task, ...(index === 1 ? { locked: true } : {}), })) as GanttTaskSourceRow[]; }
return Array.from({ length: 750 }, (_, index) => { const day = 2 + (index % 20); return { id: `large-${index + 1}`, name: `Virtualized task ${index + 1}`, startDate: `2026-02-${String(day).padStart(2, '0')}` as ISODateString, duration: 1 + (index % 5), durationUnit: 'day', percentDone: (index * 7) % 101, taskMode: 'manual', locked: index % 17 === 0, } satisfies GanttTaskSourceRow; });}
export function productionSourceRows( project: ProjectSnapshot,): GanttTaskSourceRow[] { return project.tasks.map((task) => ({ ...task, durationUnit: task.durationUnit ?? 'hour', percentDone: task.progressPercent, taskMode: task.manuallyScheduled ? 'manual' : 'auto', }));}
export async function getProductionProject( grid: HTMLRevoGridElement,): Promise<ProjectSnapshot> { return getGanttScheduleSnapshot(grid);}
export function downloadProductionSnapshot(project: ProjectSnapshot): void { const href = URL.createObjectURL( new Blob([exportProjectSnapshot(project, { space: 2 })], { type: 'application/json', }), ); const link = document.createElement('a'); link.href = href; link.download = 'gantt-project.json'; link.click(); setTimeout(() => URL.revokeObjectURL(href), 0);}
/** A real REST recipe with an injected browser-memory transport; no server is contacted. */export function createProductionPersistence() { let savedJson: string | undefined; const adapter = new ProjectSnapshotRestAdapter({ url: '/example/project', fetchImpl: (async (_input, init) => { if (init?.method === 'PUT') { savedJson = String(init.body); return new Response(null, { status: 204 }); } return new Response(savedJson ?? 'No saved project', { status: savedJson ? 200 : 404, }); }) as typeof fetch, }); return { save: async (grid: HTMLRevoGridElement) => adapter.save(await getProductionProject(grid)), load: () => adapter.load(), importFile: async (file: Pick<File, 'text'>): Promise<ProjectSnapshot> => { const parsed = parseProjectSnapshotJson(await file.text()); if (parsed.ok === false) throw new Error(parsed.error); return parsed.project; }, exportFile: async (grid: HTMLRevoGridElement) => downloadProductionSnapshot(await getProductionProject(grid)), print: (grid: HTMLRevoGridElement) => { const result = printGanttContainer(grid.parentElement, { cleanupDelayMs: 60_000, hideSelectors: [ '.gantt-production-controls', '.gantt-production-status', '.gantt-production-toolbar', '.gantt-toolbar', '.rgContextMenu', ], metadata: { title: grid.gantt?.name ?? 'Project schedule' }, }); if (!result.ok) throw new Error('The print preview could not be opened.'); }, };}
export function productionError(error: unknown): string { return error instanceof Error ? error.message : 'The operation failed.';}These recipes keep transport and framework ownership in your application. Inject fetch, state, database clients, DOM targets, and file readers/writers from your host app.
The application recipes below live in the examples source browser. Copy their recipes/ files beside your code before using the relative imports; they are maintained examples rather than published Gantt exports.
import { cloneProjectSnapshot, exportProjectSnapshot, parseProjectSnapshotJson,} from '@revolist/gantt';import { ProjectSnapshotGraphqlAdapter } from './recipes/graphql-adapter';import { ProjectSnapshotRestAdapter } from './recipes/rest-adapter';import { ProjectSnapshotSupabaseAdapter, buildCreateProjectSnapshotTableSql, buildLoadProjectSnapshotSql, buildSaveProjectSnapshotSql,} from './recipes/postgres-persistence';import { createJsonSnapshotRecipe, createPrintPdfExportRecipe,} from './recipes/persistence-examples';import { printGanttContainer } from './recipes/print-pdf-recipe';Shared server reads and committed writes
Section titled “Shared server reads and committed writes”Use the published createGanttQueryAdapter with existing load(request) recipes
for native polling, retries and shared requests. Hydrate complete projects with
GanttPlugin.setProjectSnapshot(project, options) and capture custom row fields
with getProjectData(). See Gantt query usage and configuration types
and committed commands, revisions and compensating undo/redo.
Existing save(project, request) callbacks remain application-owned.
JSON Snapshot Helpers
Section titled “JSON Snapshot Helpers”Use JSON helpers for local import/export, file handoff, tests, undo buffers, or backend payloads.
const draft = cloneProjectSnapshot(project);const json = exportProjectSnapshot(draft, { space: 2 });const parsed = parseProjectSnapshotJson(json);
if (parsed.ok) { applyProject(parsed.project);}For file picker flows:
const recipe = createJsonSnapshotRecipe({ readText: () => fileInputText, writeText: (_fileName, contents) => saveTextFile(contents),});
await recipe.save(project, 2);const imported = await recipe.load();REST Persistence
Section titled “REST Persistence”ProjectSnapshotRestAdapter loads and saves serialized project snapshots through an injected fetchImpl.
const adapter = new ProjectSnapshotRestAdapter({ url: '/api/gantt/project/demo', headers: { authorization: 'Bearer token' }, fetchImpl, space: 2,});
const project = await adapter.load();await adapter.save(project);GraphQL Persistence
Section titled “GraphQL Persistence”Use the GraphQL adapter when the backend exposes project snapshots through operations.
const adapter = new ProjectSnapshotGraphqlAdapter({ url: '/graphql', fetchImpl, loadQuery: 'query LoadProject($id: ID!) { projectSnapshot(id: $id) }', saveMutation: 'mutation SaveProject($id: ID!, $snapshot: String!) { saveProjectSnapshot(id: $id, snapshot: $snapshot) }',});
const project = await adapter.load({ variables: { id: 'demo' } });await adapter.save(project, { variables: { id: 'demo' } });Supabase And PostgreSQL
Section titled “Supabase And PostgreSQL”Use SQL builders with any PostgreSQL executor, or the Supabase-like adapter with a compatible client shape.
await executor.query(buildCreateProjectSnapshotTableSql());await executor.query(buildSaveProjectSnapshotSql(project));
const result = await executor.query(buildLoadProjectSnapshotSql(project.id));const adapter = new ProjectSnapshotSupabaseAdapter(supabaseLikeClient);
await adapter.save(project);const restored = await adapter.load(project.id);Print And Save As PDF
Section titled “Print And Save As PDF”The print recipe prepares a Gantt container for browser printing. Users can choose Print or Save as PDF in the native dialog.
const result = printGanttContainer(host, { document, window, metadata: { title: project.name, subtitle: `${project.tasks.length} tasks`, range: { startDate: '2026-05-01', endDate: '2026-05-31' }, },});For a reusable command:
const printRecipe = createPrintPdfExportRecipe({ target: host, document, window,});
printRecipe.exportProject(project, { label: 'May reporting window' });Framework Examples
Section titled “Framework Examples”Use the complete public-package workflow for your primary wrapper:
Svelte remains a secondary integration recipe. Use the custom-element approach from the Vanilla guide until its packaged wrapper and support contract match the four primary framework paths.
The Resource Planning demo shows a host-owned resource filter UI. Filtering updates grid.gantt.resourceFilterIds without reading rendered cells.
Resource filtering updates grid.gantt.resourceFilterIds:
grid.gantt = { ...project, resourceFilterIds: selectedResourceIds.length === allResourceIds.length ? undefined : selectedResourceIds,};