Vue/State management
Loading live demo…from dhtmlx.github.io
Vue
State management
Keep Gantt zoom level, undo, and redo state in a Pinia store.
- Demo.vue
- stores/ganttStore.ts
- shared/useDemoBatchState.ts
src/examples/state-management/Demo.vue View on GitHub
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { VueGantt } from "@dhtmlx/trial-vue-gantt";
import "@dhtmlx/trial-vue-gantt/dist/vue-gantt.css";
import { useGanttStore } from "../../stores/ganttStore";
import { createBatchSaveData } from "../shared/useDemoBatchState";
const ganttStore = useGanttStore();
const { tasks, links, config, zoomLevel, canUndo, canRedo } = storeToRefs(ganttStore);
const data = createBatchSaveData(changes => ganttStore.applyBatch(changes));
const setZoom = (level: "day" | "month" | "year") => {
ganttStore.setZoom(level);
};
</script>
<template>
<section class="demo-panel" data-cy="state-management-demo">
<div class="demo-toolbar">
<button data-cy="state-undo" type="button" :disabled="!canUndo" @click="ganttStore.undo()">Undo</button>
<button data-cy="state-redo" type="button" :disabled="!canRedo" @click="ganttStore.redo()">Redo</button>
<button data-cy="zoom-day" :class="{ active: zoomLevel === 'day' }" type="button" @click="setZoom('day')">
Day
</button>
<button data-cy="zoom-month" :class="{ active: zoomLevel === 'month' }" type="button" @click="setZoom('month')">
Month
</button>
<button data-cy="zoom-year" :class="{ active: zoomLevel === 'year' }" type="button" @click="setZoom('year')">
Year
</button>
</div>
<VueGantt class="demo-gantt" :tasks="tasks" :links="links" :config="config" :data="data" />
</section>
</template>
src/stores/ganttStore.ts View on GitHub
import { defineStore } from "pinia";
import type { BatchChanges, Link, Task, ZoomLevel as GanttZoomLevel } from "@dhtmlx/trial-vue-gantt";
import { applyBatchChanges } from "../examples/shared/applyBatchChanges";
import { createProjectData } from "../examples/shared/projectData";
type ZoomLevel = "day" | "month" | "year";
type Snapshot = {
tasks: Task[];
links: Link[];
zoomLevel: ZoomLevel;
};
type HistoryState = {
tasks: Task[];
links: Link[];
zoomLevel: ZoomLevel;
past: Snapshot[];
future: Snapshot[];
maxHistory: number;
};
const zoomLevels: GanttZoomLevel[] = [
{
name: "day",
scale_height: 27,
min_column_width: 80,
scales: [{ unit: "day", step: 1, format: "%d %M" }]
},
{
name: "month",
scale_height: 50,
min_column_width: 120,
scales: [
{ unit: "month", format: "%F, %Y" },
{ unit: "week", format: "Week #%W" }
]
},
{
name: "year",
scale_height: 50,
min_column_width: 36,
scales: [{ unit: "year", step: 1, format: "%Y" }]
}
];
const cloneDate = (value: Date | undefined): Date | undefined => {
if (value instanceof Date) {
return new Date(value.getTime());
}
return value;
};
const cloneTask = (task: Task): Task => {
const next: Task = { ...task };
next.start_date = cloneDate(task.start_date);
next.end_date = cloneDate(task.end_date);
return next;
};
const cloneLink = (link: Link): Link => ({ ...link });
const createSnapshot = (state: HistoryState): Snapshot => ({
tasks: state.tasks.map(cloneTask),
links: state.links.map(cloneLink),
zoomLevel: state.zoomLevel
});
export const useGanttStore = defineStore("gantt", {
state: () => {
const data = createProjectData();
return {
tasks: data.tasks as Task[],
links: data.links as Link[],
zoomLevel: "day" as ZoomLevel,
past: [] as Snapshot[],
future: [] as Snapshot[],
maxHistory: 50
};
},
getters: {
config: state => ({
zoom: {
current: state.zoomLevel,
levels: zoomLevels
}
}),
canUndo: state => state.past.length > 0,
canRedo: state => state.future.length > 0
},
actions: {
pushHistory() {
this.past = [...this.past, createSnapshot(this)];
if (this.past.length > this.maxHistory) {
this.past = this.past.slice(this.past.length - this.maxHistory);
}
this.future = [];
},
restoreSnapshot(snapshot: Snapshot) {
this.tasks = snapshot.tasks.map(cloneTask);
this.links = snapshot.links.map(cloneLink);
this.zoomLevel = snapshot.zoomLevel;
},
setZoom(level: ZoomLevel) {
if (this.zoomLevel === level) {
return;
}
this.pushHistory();
this.zoomLevel = level;
},
applyBatch(changes: BatchChanges) {
const hasChanges = (changes.tasks?.length ?? 0) > 0 || (changes.links?.length ?? 0) > 0;
if (!hasChanges) {
return;
}
this.pushHistory();
const next = applyBatchChanges(this.tasks, this.links, changes);
this.tasks = next.tasks;
this.links = next.links;
},
undo() {
if (this.past.length === 0) {
return;
}
const previous = this.past[this.past.length - 1];
const current = createSnapshot(this);
this.past = this.past.slice(0, -1);
this.future = [current, ...this.future];
this.restoreSnapshot(previous);
},
redo() {
if (this.future.length === 0) {
return;
}
const next = this.future[0];
const current = createSnapshot(this);
this.future = this.future.slice(1);
this.past = [...this.past, current];
if (this.past.length > this.maxHistory) {
this.past = this.past.slice(this.past.length - this.maxHistory);
}
this.restoreSnapshot(next);
}
}
});
src/examples/shared/useDemoBatchState.ts View on GitHub
import { ref } from "vue";
import type { BatchChanges, Link, Task } from "@dhtmlx/trial-vue-gantt";
import { applyBatchChanges } from "./applyBatchChanges";
export interface DemoDataSet {
tasks: Task[];
links: Link[];
}
export function createBatchSaveData(applyBatch: (changes: BatchChanges) => void) {
return {
batchSave: applyBatch
};
}
export function useDemoBatchState(initialData: DemoDataSet) {
const tasks = ref<Task[]>(initialData.tasks);
const links = ref<Link[]>(initialData.links);
const applyBatch = (changes: BatchChanges) => {
const next = applyBatchChanges(tasks.value, links.value, changes);
tasks.value = next.tasks;
links.value = next.links;
};
return {
tasks,
links,
applyBatch,
data: createBatchSaveData(applyBatch)
};
}