How Does the Undo/Redo Functionality Work with Pinia Store in Vue Color Avatar
The vue-color-avatar editor implements time-travel history using a Pinia store that maintains past, present, and future arrays, enabling unlimited undo and redo through state mutation actions.
The vue-color-avatar repository provides a vector-style avatar generator built with Vue 3 and TypeScript. Understanding how the undo/redo functionality works with Pinia store reveals a classic command-pattern implementation that tracks state changes through immutable history stacks.
Understanding the History State Structure
The Pinia store defined in src/store/index.ts structures history as three distinct collections that represent the timeline of user interactions.
| Property | Purpose |
|---|---|
past |
Array of previous AvatarOption values in chronological order |
present |
The current AvatarOption actively displayed in the UI |
future |
Array of AvatarOption values that were undone and can be restored |
// src/store/index.ts (lines 16-20)
export interface State {
history: {
past: AvatarOption[]
present: AvatarOption
future: AvatarOption[]
}
isSiderCollapsed: boolean
}
Recording State Changes with SET_AVATAR_OPTION
When users modify avatar settings, the [SET_AVATAR_OPTION] action updates the history stack. This action implements the core logic for branching timelines by clearing the future array whenever a new state is committed.
// src/store/index.ts (lines 35-41)
[SET_AVATAR_OPTION](data: AvatarOption) {
this.history = {
past: [...this.history.past, this.history.present],
present: data,
future: [],
}
}
Key behavior: The spread operator creates immutable copies of the past array, ensuring Reactivity works correctly while the future array resets to empty. This prevents redo operations from jumping to obsolete states after new modifications.
Implementing Undo and Redo Actions
The store exposes [UNDO] and [REDO] actions that manipulate the three history collections to navigate through the state timeline.
The UNDO Action
The [UNDO] action moves the current state into the future stack and restores the most recent previous state.
// src/store/index.ts (lines 43-52)
[UNDO]() {
if (this.history.past.length > 0) {
const previous = this.history.past[this.history.past.length - 1]
const newPast = this.history.past.slice(0, this.history.past.length - 1)
this.history = {
past: newPast,
present: previous,
future: [this.history.present, ...this.history.future],
}
}
}
Execution flow:
- Verify
pastcontains at least one entry - Extract the last element as the
previousstate - Slice
pastto remove the retrieved element - Prepend current
presenttofuturearray - Assign
previousas newpresent
The REDO Action
The [REDO] action reverses an undo by shifting states from future back to present and pushing the current state onto past.
// src/store/index.ts (lines 55-64)
[REDO]() {
if (this.history.future.length > 0) {
const next = this.history.future[0]
const newFuture = this.history.future.slice(1)
this.history = {
past: [...this.history.past, this.history.present],
present: next,
future: newFuture,
}
}
}
Execution flow:
- Verify
futurecontains at least one entry - Extract the first element as the
nextstate - Slice
futureto remove the retrieved element - Append current
presenttopastarray - Assign
nextas newpresent
Integrating Undo/Redo in Vue Components
Components interact with the history system through the store's typed actions and reactive state. The mutation constants in src/store/mutation-type.ts provide type-safe access to action names.
// Component integration example
import { useStore } from '@/store'
import { SET_AVATAR_OPTION, UNDO, REDO } from '@/store/mutation-type'
const store = useStore()
// Apply a new avatar configuration
function updateAvatar(option: AvatarOption) {
store[SET_AVATAR_OPTION](option)
}
// History navigation handlers
function handleUndo() {
store[UNDO]()
}
function handleRedo() {
store[REDO]()
}
Checking History Availability
UI controls should disable undo/redo buttons when the respective history stacks are empty. Compute these states using the past and future array lengths.
import { computed } from 'vue'
const canUndo = computed(() => store.history.past.length > 0)
const canRedo = computed(() => store.history.future.length > 0)
Summary
- The Pinia store in
src/store/index.tsimplements undo/redo through three reactive arrays:past,present, andfuture. - Immutable updates using the spread operator ensure Reactivity works correctly when shifting states between history stacks.
- Branching timeline logic clears the
futurearray whenever[SET_AVATAR_OPTION]commits a new state, preventing invalid redo operations. - Type-safe actions use constants from
src/store/mutation-type.tsto expose[UNDO]and[REDO]methods that manipulate the history collections. - UI integration relies on computed properties checking
store.history.past.lengthandstore.history.future.lengthto enable or disable history controls.
Frequently Asked Questions
How is the history state initialized in the Pinia store?
The history state initializes with an empty past array, a present value generated by getRandomAvatarOption() from src/utils/index.ts, and an empty future array. This setup provides a valid initial avatar configuration while maintaining empty history stacks ready to record user interactions.
Why does the future array get cleared when setting a new avatar option?
The [SET_AVATAR_OPTION] action clears the future array to maintain a linear timeline. When users undo several steps and then make a new change, the previous future states become invalid because they represent a timeline that no longer exists. Clearing future prevents the UI from offering redo operations that would jump to obsolete configurations.
Can the undo/redo history be limited to a specific number of steps?
The current implementation in src/store/index.ts does not impose a limit on the past or future array lengths. However, you could extend the [SET_AVATAR_OPTION] action to check this.history.past.length and use slice() to keep only the most recent N entries, preventing memory growth during long editing sessions.
How do I check if undo or redo actions are available in the UI?
Create computed properties that evaluate the length of the history arrays. Check store.history.past.length > 0 to determine if undo is available, and store.history.future.length > 0 for redo availability. Bind these booleans to the disabled attributes of your undo and redo buttons to provide clear visual feedback to users.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →