Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

Commit

Permalink
feat: use immer for history
Browse files Browse the repository at this point in the history
  • Loading branch information
neko-para committed Aug 5, 2023
1 parent 5fc2b4d commit aa831d2
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 43 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"preview": "vite preview",
"build-only": "vite build",
"build-watch": "vite build --watch",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false"
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"backend": "cd server && npm run run"
},
"dependencies": {
"vue": "^3.3.4"
Expand All @@ -24,6 +25,7 @@
"autoprefixer": "^10.4.14",
"axios": "^1.4.0",
"buffer": "^6.0.3",
"immer": "^10.0.2",
"jszip": "^3.10.1",
"naive-ui": "^2.34.4",
"npm-run-all": "^4.1.5",
Expand Down
26 changes: 9 additions & 17 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,7 @@ import {
import { taskData, active, navigate } from './data'
import TaskEdit from '@/components/TaskEdit.vue'
import TaskTree from '@/components/TaskTree.vue'
import {
history,
historyPush,
historyUndo,
historyRedo,
canUndo,
canRedo
} from './history'
import { history } from './history'
import { loadData, syncData } from './loader'
const expands = ref<string[]>(['root.'])
Expand All @@ -40,18 +33,17 @@ onMounted(() => {
})
const someBackward = computed(() => {
return history.backward.slice(-5).slice(0, -1)
return history.info.prev[0].value.slice(-4).map(x => x.active)
})
const someForward = computed(() => {
return history.forward.slice(-4).reverse()
return history.info.next[0].value
.slice(-4)
.reverse()
.map(x => x.active)
})
const fastNavigate = computed<number>({
set(ofs: number) {
if (ofs < 0) {
history.forward.push(...history.backward.splice(ofs).reverse())
} else {
history.backward.push(...history.forward.splice(-ofs).reverse())
}
history.move(ofs)
},
get() {
return 0
Expand All @@ -62,14 +54,14 @@ const fastNavigate = computed<number>({
<template>
<div class="flex flex-col gap-2 flex-1 min-h-0">
<div class="flex gap-2">
<NButton :disabled="!canUndo" @click="historyUndo()">
<NButton :disabled="!history.canUndo()" @click="history.undo()">
<template #icon>
<NIcon>
<NavigateBeforeOutlined></NavigateBeforeOutlined>
</NIcon>
</template>
</NButton>
<NButton :disabled="!canRedo" @click="historyRedo()">
<NButton :disabled="!history.canRedo()" @click="history.redo()">
<template #icon>
<NIcon>
<NavigateNextOutlined></NavigateNextOutlined>
Expand Down
9 changes: 3 additions & 6 deletions src/data.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { type TreeOption } from 'naive-ui'
import { computed, reactive } from 'vue'
import type { Task } from './types'
import { history, historyPush } from './history'
import { history } from './history'
import { buildEntries, type FSEntry } from './fs'

export interface TaskData {
[task: string]: Task
}

export const folderData = reactive<{ data: string[][] }>({ data: [] })

export const fileData = reactive<{ data: string[][] }>({ data: [] })

export const taskData = reactive<{ data: TaskData }>({ data: {} })
Expand All @@ -23,13 +22,11 @@ export function isModified(task: string) {
}

export const active = computed(() => {
return history.backward.length > 0
? history.backward[history.backward.length - 1]
: null
return history.current.value
})

export function navigate(task: string) {
historyPush(task)
history.push(task)
}

export const taskTree = computed<TreeOption>(() => {
Expand Down
55 changes: 38 additions & 17 deletions src/history.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
import { computed, reactive } from 'vue'
import { computed, type ComputedRef } from 'vue'
import { Persis } from './persis'

export const history = reactive<{
backward: string[]
forward: string[]
}>({ backward: [], forward: [] })
class History {
info: Persis<{ active: string | null }>
current: ComputedRef<string | null>

export function historyPush(v: string) {
history.backward.push(v)
history.forward = []
}
constructor() {
this.info = new Persis({
active: null
})
this.current = computed(() => {
return this.info.now().value.active
})
}

export const canUndo = computed(() => history.backward.length > 0)
export const canRedo = computed(() => history.forward.length > 0)
push(v: string) {
this.info.change(h => {
h.active = v
})
}

export function historyUndo() {
history.forward.push(history.backward.pop()!)
}
canUndo() {
return this.info.canUndo
}

canRedo() {
return this.info.canRedo
}

export function historyRedo() {
const v = history.forward.pop()!
history.backward.push(v)
undo() {
this.info.undo()
}

redo() {
this.info.redo()
}

move(ofs: number) {
this.info.move(ofs)
}
}

export const history = new History()
3 changes: 1 addition & 2 deletions src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
fileData
} from '@/data'
import axios from 'axios'
import { Buffer } from 'buffer'
import JSZip from 'jszip'

async function loadZip() {
Expand All @@ -23,7 +22,7 @@ async function loadZip() {
}

export async function loadData() {
loadZip()
// loadZip()
const entry = (await axios.post('/api/list')).data as {
success: boolean
data: {
Expand Down
99 changes: 99 additions & 0 deletions src/persis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { produce } from 'immer'
import { shallowRef, type ComputedRef, type ShallowRef, computed } from 'vue'

type ImmerInfo<T> = [ShallowRef<T>, (updater: (target: T) => void) => void]

function useImmer<T>(init: T): ImmerInfo<T> {
const state = shallowRef<T>(init)
const update = (updater: (target: T) => void) => {
state.value = produce(state.value, updater)
}

return [state, update]
}

export class Persis<T> {
current: ImmerInfo<T>
prev: ImmerInfo<T[]>
next: ImmerInfo<T[]>

canUndo: ComputedRef<boolean>
canRedo: ComputedRef<boolean>

constructor(data: T) {
this.current = useImmer(data)
this.prev = useImmer([])
this.next = useImmer([])

this.canUndo = computed(() => {
return this.prev[0].value.length > 0
})
this.canRedo = computed(() => {
return this.next[0].value.length > 0
})
}

now() {
return this.current[0]
}

change(proc: (v: T) => void) {
this.next[0].value = []
this.prev[1](draft => {
draft.push(this.current[0].value)
})
this.current[1](proc)
}

move(offset: number) {
if (offset < 0) {
if (this.prev[0].value.length < -offset) {
return
}

this.next[1](draft => {
draft.push(this.current[0].value)
if (-offset > 1) {
draft.push(...this.prev[0].value.slice(offset + 1).reverse())
}
})

this.current[0].value =
this.prev[0].value[this.prev[0].value.length + offset]

this.prev[1](draft => {
draft.splice(offset)
})
} else if (offset > 0) {
if (this.next[0].value.length < offset) {
return
}

this.prev[1](draft => {
draft.push(this.current[0].value)
if (offset > 1) {
draft.push(...this.next[0].value.slice(-offset + 1).reverse())
}
})

this.current[0].value =
this.next[0].value[this.next[0].value.length - offset]

this.next[1](draft => {
draft.splice(-offset)
})
}
}

undo() {
if (this.canUndo.value) {
this.move(-1)
}
}

redo() {
if (this.canRedo.value) {
this.move(1)
}
}
}

0 comments on commit aa831d2

Please sign in to comment.