Skip to content

Commit

Permalink
WIP: Undo system
Browse files Browse the repository at this point in the history
ref #719
  • Loading branch information
frostburn committed Jun 11, 2024
1 parent b97516d commit 40acde4
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 3 deletions.
18 changes: 18 additions & 0 deletions src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,21 @@ div.alert-box-danger {
p.alert-message-danger {
color: var(--color-alert-danger);
}

.undo,
.redo {
cursor: pointer;
}

.undo.disabled,
.redo.disabled {
cursor: default;
color: var(--color-text-mute);
}

.undo::after {
content: '↺';
}
.redo::after {
content: '↻';
}
20 changes: 19 additions & 1 deletion src/components/ScaleControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,17 @@ defineExpose({ focus, clearPaletteInfo })
</div>

<div class="control-group">
<h2>Scale data</h2>
<h2>
<span class="scale-data-header">Scale data</span>
<span
:class="{ undo: true, disabled: scale.undoDisabled }"
@click="scale.history.undo"
></span>
<span
:class="{ redo: true, disabled: scale.redoDisabled }"
@click="scale.history.redo"
></span>
</h2>
<div class="control">
<textarea
id="scale-data"
Expand Down Expand Up @@ -149,4 +159,12 @@ defineExpose({ focus, clearPaletteInfo })
height: 3em;
overflow-y: hidden;
}
.scale-data-header {
pointer-events: none;
user-select: none;
}
.undo,
.redo {
margin-left: 1em;
}
</style>
32 changes: 30 additions & 2 deletions src/stores/scale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
randomId
} from '@/utils'
import { defineStore } from 'pinia'
import { computed, ref, watch } from 'vue'
import { computed, reactive, ref, watch } from 'vue'
import { Fraction, mmod, mtof } from 'xen-dev-utils'
import {
parseAST,
Expand Down Expand Up @@ -38,6 +38,7 @@ import {
import { pianoMap } from 'isomorphic-qwerty'
import { computeWhiteIndices } from '@/midi'
import { midiKeyInfo } from 'xen-midi'
import { StoreHistory } from '@/undo'

// Colors from #1 to #12 inclusive.
function defaultColors(base: number) {
Expand Down Expand Up @@ -434,6 +435,7 @@ export const useScaleStore = defineStore('scale', () => {
const rootFrequency = autoFrequency.value ? 'mtof(_)' : 'baseFrequency'
warning.value = `Base MIDI note ${noteNumber} defaults to ${midiName}. Use an explicit ${enharmonics[0]} = ${rootFrequency} or ${enharmonics[1]} = ${rootFrequency} to get rid of this warning.`
}
history.pushState()
} catch (e) {
if (e instanceof Error) {
error.value = e.message
Expand Down Expand Up @@ -567,6 +569,28 @@ export const useScaleStore = defineStore('scale', () => {
uploadedId.value = data['id']
}

function serialize() {
const data = toJSON()
data.id = id.value
data.uploadedId = uploadedId.value
console.log(data.id)
return JSON.stringify(data)
}

function restore(body: string) {
const data = JSON.parse(body, (key, value) => Interval.reviver(key, Scale.reviver(key, value)))
fromJSON(data)
uploadedId.value = data.uploadedId
// TODO: Figure out the interaction between undo history and server uploading
console.log(id.value, uploadedId.value)
}

const history = reactive(new StoreHistory(serialize, restore))

const undoDisabled = computed(() => history.pointer <= 0)

const redoDisabled = computed(() => history.pointer >= history.states.length - 1)

return {
// Live state
...LIVE_STATE,
Expand Down Expand Up @@ -599,6 +623,10 @@ export const useScaleStore = defineStore('scale', () => {
colorForIndex,
labelForIndex,
toJSON,
fromJSON
fromJSON,
// Class instances
history,
undoDisabled,
redoDisabled
}
})
44 changes: 44 additions & 0 deletions src/undo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
type Serializer = () => string
type Restorer = (state: string) => void

export class StoreHistory {
states: string[]
pointer: number
capacity: number
serialize: Serializer
restore: Restorer

constructor(serialize: Serializer, restore: Restorer, capacity = 10) {
this.states = []
this.pointer = -1
this.capacity = capacity
this.serialize = serialize
this.restore = restore
this.pushState()
}

pushState() {
this.states = this.states.slice(0, this.pointer + 1)
this.states.push(this.serialize())
this.states = this.states.slice(-this.capacity)
this.pointer = this.states.length - 1
}

undo() {
this.pointer--
if (this.pointer < 0) {
this.pointer = -1
return
}
this.restore(this.states[this.pointer])
}

redo() {
this.pointer++
if (this.pointer >= this.states.length) {
this.pointer = this.states.length - 1
return
}
this.restore(this.states[this.pointer])
}
}

0 comments on commit 40acde4

Please sign in to comment.