Improve copy and paste to support keeping all data bindings valid
This commit is contained in:
parent
5f390383b4
commit
e411354538
|
@ -1,4 +1,10 @@
|
|||
import { store } from "./index"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import {
|
||||
decodeJSBinding,
|
||||
encodeJSBinding,
|
||||
findAllBindings,
|
||||
} from "@budibase/string-templates"
|
||||
|
||||
/**
|
||||
* Recursively searches for a specific component ID
|
||||
|
@ -161,3 +167,58 @@ export const getComponentSettings = componentType => {
|
|||
|
||||
return settings
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomises a components ID's, including all child component IDs, and also
|
||||
* updates all data bindings to still be valid.
|
||||
* This mutates the object in place.
|
||||
* @param component the component to randomise
|
||||
*/
|
||||
export const makeComponentUnique = component => {
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
|
||||
// Replace component ID
|
||||
const oldId = component._id
|
||||
const newId = Helpers.uuid()
|
||||
component._id = newId
|
||||
|
||||
if (component._children?.length) {
|
||||
let children = JSON.stringify(component._children)
|
||||
|
||||
// Replace all instances of this ID in child HBS bindings
|
||||
children = children.replace(new RegExp(oldId, "g"), newId)
|
||||
|
||||
// Replace all instances of this ID in child JS bindings
|
||||
const bindings = findAllBindings(children)
|
||||
bindings.forEach(binding => {
|
||||
// JSON.stringify will have escaped double quotes, so we need
|
||||
// to account for that
|
||||
let sanitizedBinding = binding.replace(/\\"/g, '"')
|
||||
|
||||
// Check if this is a valid JS binding
|
||||
let js = decodeJSBinding(sanitizedBinding)
|
||||
if (js != null) {
|
||||
// Replace ID inside JS binding
|
||||
js = js.replace(new RegExp(oldId, "g"), newId)
|
||||
|
||||
// Create new valid JS binding
|
||||
let newBinding = encodeJSBinding(js)
|
||||
|
||||
// Replace escaped double quotes
|
||||
newBinding = newBinding.replace(/"/g, '\\"')
|
||||
|
||||
// Insert new JS back into binding.
|
||||
// A single string replace here is better than a regex as
|
||||
// the binding contains special characters, and we only need
|
||||
// to replace a single instance.
|
||||
children = children.replace(binding, newBinding)
|
||||
}
|
||||
})
|
||||
|
||||
// Recurse on all children
|
||||
component._children = JSON.parse(children)
|
||||
component._children.forEach(makeComponentUnique)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,9 +24,9 @@ import {
|
|||
findAllMatchingComponents,
|
||||
findComponent,
|
||||
getComponentSettings,
|
||||
makeComponentUnique,
|
||||
} from "../componentUtils"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { removeBindings } from "../dataBinding"
|
||||
|
||||
const INITIAL_FRONTEND_STATE = {
|
||||
apps: [],
|
||||
|
@ -490,37 +490,22 @@ export const getFrontendStore = () => {
|
|||
}
|
||||
}
|
||||
},
|
||||
paste: async (targetComponent, mode, preserveBindings = false) => {
|
||||
paste: async (targetComponent, mode) => {
|
||||
let promises = []
|
||||
store.update(state => {
|
||||
// Stop if we have nothing to paste
|
||||
if (!state.componentToPaste) {
|
||||
return state
|
||||
}
|
||||
|
||||
// defines if this is a copy or a cut
|
||||
const cut = state.componentToPaste.isCut
|
||||
|
||||
// immediately need to remove bindings, currently these aren't valid when pasted
|
||||
if (!cut && !preserveBindings) {
|
||||
state.componentToPaste = removeBindings(state.componentToPaste, "")
|
||||
}
|
||||
|
||||
// Clone the component to paste
|
||||
// Retain the same ID if cutting as things may be referencing this component
|
||||
// Clone the component to paste and make unique if copying
|
||||
delete state.componentToPaste.isCut
|
||||
let componentToPaste = cloneDeep(state.componentToPaste)
|
||||
if (cut) {
|
||||
state.componentToPaste = null
|
||||
} else {
|
||||
const randomizeIds = component => {
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
component._id = Helpers.uuid()
|
||||
component._children?.forEach(randomizeIds)
|
||||
}
|
||||
randomizeIds(componentToPaste)
|
||||
makeComponentUnique(componentToPaste)
|
||||
}
|
||||
|
||||
if (mode === "inside") {
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
|
||||
const duplicateComponent = () => {
|
||||
storeComponentForCopy(false)
|
||||
pasteComponent("below", true)
|
||||
pasteComponent("below")
|
||||
}
|
||||
|
||||
const deleteComponent = async () => {
|
||||
|
@ -77,10 +77,10 @@
|
|||
store.actions.components.copy(component, cut)
|
||||
}
|
||||
|
||||
const pasteComponent = (mode, preserveBindings = false) => {
|
||||
const pasteComponent = mode => {
|
||||
try {
|
||||
// lives in store - also used by drag drop
|
||||
store.actions.components.paste(component, mode, preserveBindings)
|
||||
store.actions.components.paste(component, mode)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving component")
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ module.exports.processObject = templates.processObject
|
|||
module.exports.doesContainStrings = templates.doesContainStrings
|
||||
module.exports.doesContainString = templates.doesContainString
|
||||
module.exports.disableEscaping = templates.disableEscaping
|
||||
module.exports.findAllBindings = templates.findAllBindings
|
||||
|
||||
/**
|
||||
* Use vm2 to run JS scripts in a node env
|
||||
|
|
|
@ -322,3 +322,12 @@ module.exports.doesContainStrings = (template, strings) => {
|
|||
module.exports.doesContainString = (template, string) => {
|
||||
return exports.doesContainStrings(template, [string])
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all regular (double bracketed) expressions inside a string.
|
||||
* @param string the string to search
|
||||
* @return {[]} all matching bindings
|
||||
*/
|
||||
module.exports.findAllBindings = string => {
|
||||
return findDoubleHbsInstances(string)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ export const processObject = templates.processObject
|
|||
export const doesContainStrings = templates.doesContainStrings
|
||||
export const doesContainString = templates.doesContainString
|
||||
export const disableEscaping = templates.disableEscaping
|
||||
export const findAllBindings = templates.findAllBindings
|
||||
|
||||
/**
|
||||
* Use polyfilled vm to run JS scripts in a browser Env
|
||||
|
|
Loading…
Reference in New Issue