Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert history store #15408

Merged
merged 23 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/builder/src/stores/builder/automations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { derived, get } from "svelte/store"
import { API } from "@/api"
import { cloneDeep } from "lodash/fp"
import { generate } from "shortid"
import { createHistoryStore } from "@/stores/builder/history"
import { createHistoryStore, HistoryStore } from "@/stores/builder/history"
import { licensing } from "@/stores/portal"
import { tables, appStore } from "@/stores/builder"
import { notifications } from "@budibase/bbui"
Expand Down Expand Up @@ -1428,7 +1428,7 @@ const automationActions = (store: AutomationStore) => ({
})

class AutomationStore extends BudiStore<AutomationState> {
history: any
history: HistoryStore<Automation>
actions: ReturnType<typeof automationActions>

constructor() {
Expand Down
2 changes: 1 addition & 1 deletion packages/builder/src/stores/builder/componentTreeNodes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { get } from "svelte/store"
import { selectedScreen as selectedScreenStore } from "./screens"
import { findComponentPath } from "@/helpers/components"
import { Screen, Component } from "@budibase/types"
import { Component, Screen } from "@budibase/types"
import { BudiStore, PersistenceType } from "@/stores/BudiStore"

interface OpenNodesState {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import * as jsonpatch from "fast-json-patch/index.mjs"
import { writable, derived, get } from "svelte/store"
import { Document } from "@budibase/types"
import * as jsonpatch from "fast-json-patch"
import { writable, derived, get, Readable } from "svelte/store"

export const Operations = {
Add: "Add",
Delete: "Delete",
Change: "Change",
export const enum Operations {
Add = "Add",
Delete = "Delete",
Change = "Change",
}

interface Operator<T extends Document> {
id?: number
type: Operations
doc: T
forwardPatch?: jsonpatch.Operation[]
backwardsPatch?: jsonpatch.Operation[]
}

interface HistoryState<T extends Document> {
history: Operator<T>[]
position: number
loading?: boolean
}

export const initialState = {
Expand All @@ -13,14 +28,38 @@ export const initialState = {
loading: false,
}

export const createHistoryStore = ({
export interface HistoryStore<T extends Document>
extends Readable<
HistoryState<T> & {
canUndo: boolean
canRedo: boolean
}
> {
wrapSaveDoc: (
fn: (doc: T) => Promise<T>
) => (doc: T, operationId?: number) => Promise<T>
wrapDeleteDoc: (
fn: (doc: T) => Promise<void>
) => (doc: T, operationId?: number) => Promise<void>

reset: () => void
undo: () => Promise<void>
redo: () => Promise<void>
}

export const createHistoryStore = <T extends Document>({
getDoc,
selectDoc,
beforeAction = () => {},
afterAction = () => {},
}) => {
beforeAction,
afterAction,
}: {
getDoc: (id: string) => T | undefined
selectDoc: (id: string) => void
beforeAction?: (operation?: Operator<T>) => void
afterAction?: (operation?: Operator<T>) => void
}): HistoryStore<T> => {
// Use a derived store to check if we are able to undo or redo any operations
const store = writable(initialState)
const store = writable<HistoryState<T>>(initialState)
const derivedStore = derived(store, $store => {
return {
...$store,
Expand All @@ -31,8 +70,8 @@ export const createHistoryStore = ({

// Wrapped versions of essential functions which we call ourselves when using
// undo and redo
let saveFn
let deleteFn
let saveFn: (doc: T, operationId?: number) => Promise<T>
let deleteFn: (doc: T, operationId?: number) => Promise<void>

/**
* Internal util to set the loading flag
Expand Down Expand Up @@ -66,7 +105,7 @@ export const createHistoryStore = ({
* For internal use only.
* @param operation the operation to save
*/
const saveOperation = operation => {
const saveOperation = (operation: Operator<T>) => {
store.update(state => {
// Update history
let history = state.history
Expand All @@ -93,15 +132,15 @@ export const createHistoryStore = ({
* @param fn the save function
* @returns {function} a wrapped version of the save function
*/
const wrapSaveDoc = fn => {
saveFn = async (doc, operationId) => {
const wrapSaveDoc = (fn: (doc: T) => Promise<T>) => {
saveFn = async (doc: T, operationId?: number) => {
// Only works on a single doc at a time
if (!doc || Array.isArray(doc)) {
return
}
startLoading()
try {
const oldDoc = getDoc(doc._id)
const oldDoc = getDoc(doc._id!)
const newDoc = jsonpatch.deepClone(await fn(doc))

// Store the change
Expand Down Expand Up @@ -141,8 +180,8 @@ export const createHistoryStore = ({
* @param fn the delete function
* @returns {function} a wrapped version of the delete function
*/
const wrapDeleteDoc = fn => {
deleteFn = async (doc, operationId) => {
const wrapDeleteDoc = (fn: (doc: T) => Promise<void>) => {
deleteFn = async (doc: T, operationId?: number) => {
// Only works on a single doc at a time
if (!doc || Array.isArray(doc)) {
return
Expand Down Expand Up @@ -201,7 +240,7 @@ export const createHistoryStore = ({
// Undo ADD
if (operation.type === Operations.Add) {
// Try to get the latest doc version to delete
const latestDoc = getDoc(operation.doc._id)
const latestDoc = getDoc(operation.doc._id!)
const doc = latestDoc || operation.doc
await deleteFn(doc, operation.id)
}
Expand All @@ -219,7 +258,7 @@ export const createHistoryStore = ({
// Undo CHANGE
else {
// Get the current doc and apply the backwards patch on top of it
let doc = jsonpatch.deepClone(getDoc(operation.doc._id))
let doc = jsonpatch.deepClone(getDoc(operation.doc._id!))
if (doc) {
jsonpatch.applyPatch(
doc,
Expand Down Expand Up @@ -283,15 +322,15 @@ export const createHistoryStore = ({
// Redo DELETE
else if (operation.type === Operations.Delete) {
// Try to get the latest doc version to delete
const latestDoc = getDoc(operation.doc._id)
const latestDoc = getDoc(operation.doc._id!)
const doc = latestDoc || operation.doc
await deleteFn(doc, operation.id)
}

// Redo CHANGE
else {
// Get the current doc and apply the forwards patch on top of it
let doc = jsonpatch.deepClone(getDoc(operation.doc._id))
let doc = jsonpatch.deepClone(getDoc(operation.doc._id!))
if (doc) {
jsonpatch.applyPatch(doc, jsonpatch.deepClone(operation.forwardPatch))
await saveFn(doc, operation.id)
Expand Down
19 changes: 11 additions & 8 deletions packages/builder/src/stores/builder/screens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
navigationStore,
selectedComponent,
} from "@/stores/builder"
import { createHistoryStore } from "@/stores/builder/history"
import { createHistoryStore, HistoryStore } from "@/stores/builder/history"
import { API } from "@/api"
import { BudiStore } from "../BudiStore"
import {
Expand All @@ -33,9 +33,9 @@ export const initialScreenState: ScreenState = {

// Review the nulls
export class ScreenStore extends BudiStore<ScreenState> {
history: any
delete: any
save: any
history: HistoryStore<Screen>
delete: (screens: Screen) => Promise<void>
save: (screen: Screen) => Promise<Screen>

constructor() {
super(initialScreenState)
Expand Down Expand Up @@ -280,7 +280,10 @@ export class ScreenStore extends BudiStore<ScreenState> {
* supports deeply mutating the current doc rather than just appending data.
*/
sequentialScreenPatch = Utils.sequential(
async (patchFn: (screen: Screen) => any, screenId: string) => {
async (
patchFn: (screen: Screen) => boolean,
screenId: string
): Promise<Screen | void> => {
const state = get(this.store)
const screen = state.screens.find(screen => screen._id === screenId)
if (!screen) {
Expand Down Expand Up @@ -361,10 +364,10 @@ export class ScreenStore extends BudiStore<ScreenState> {
* Any deleted screens will then have their routes/links purged
*
* Wrapped by {@link delete}
* @param {Screen | Screen[]} screens
* @param {Screen } screens
*/
async deleteScreen(screens: Screen | Screen[]) {
const screensToDelete = Array.isArray(screens) ? screens : [screens]
async deleteScreen(screen: Screen) {
const screensToDelete = [screen]
// Build array of promises to speed up bulk deletions
let promises: Promise<DeleteScreenResponse>[] = []
let deleteUrls: string[] = []
Expand Down
Loading
Loading