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 { store } from "./index"
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
import {
|
||||||
|
decodeJSBinding,
|
||||||
|
encodeJSBinding,
|
||||||
|
findAllBindings,
|
||||||
|
} from "@budibase/string-templates"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively searches for a specific component ID
|
* Recursively searches for a specific component ID
|
||||||
|
@ -161,3 +167,58 @@ export const getComponentSettings = componentType => {
|
||||||
|
|
||||||
return settings
|
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,
|
findAllMatchingComponents,
|
||||||
findComponent,
|
findComponent,
|
||||||
getComponentSettings,
|
getComponentSettings,
|
||||||
|
makeComponentUnique,
|
||||||
} from "../componentUtils"
|
} from "../componentUtils"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { removeBindings } from "../dataBinding"
|
|
||||||
|
|
||||||
const INITIAL_FRONTEND_STATE = {
|
const INITIAL_FRONTEND_STATE = {
|
||||||
apps: [],
|
apps: [],
|
||||||
|
@ -490,37 +490,22 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
paste: async (targetComponent, mode, preserveBindings = false) => {
|
paste: async (targetComponent, mode) => {
|
||||||
let promises = []
|
let promises = []
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
// Stop if we have nothing to paste
|
// Stop if we have nothing to paste
|
||||||
if (!state.componentToPaste) {
|
if (!state.componentToPaste) {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
// defines if this is a copy or a cut
|
|
||||||
const cut = state.componentToPaste.isCut
|
const cut = state.componentToPaste.isCut
|
||||||
|
|
||||||
// immediately need to remove bindings, currently these aren't valid when pasted
|
// Clone the component to paste and make unique if copying
|
||||||
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
|
|
||||||
delete state.componentToPaste.isCut
|
delete state.componentToPaste.isCut
|
||||||
let componentToPaste = cloneDeep(state.componentToPaste)
|
let componentToPaste = cloneDeep(state.componentToPaste)
|
||||||
if (cut) {
|
if (cut) {
|
||||||
state.componentToPaste = null
|
state.componentToPaste = null
|
||||||
} else {
|
} else {
|
||||||
const randomizeIds = component => {
|
makeComponentUnique(componentToPaste)
|
||||||
if (!component) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
component._id = Helpers.uuid()
|
|
||||||
component._children?.forEach(randomizeIds)
|
|
||||||
}
|
|
||||||
randomizeIds(componentToPaste)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode === "inside") {
|
if (mode === "inside") {
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
const duplicateComponent = () => {
|
const duplicateComponent = () => {
|
||||||
storeComponentForCopy(false)
|
storeComponentForCopy(false)
|
||||||
pasteComponent("below", true)
|
pasteComponent("below")
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteComponent = async () => {
|
const deleteComponent = async () => {
|
||||||
|
@ -77,10 +77,10 @@
|
||||||
store.actions.components.copy(component, cut)
|
store.actions.components.copy(component, cut)
|
||||||
}
|
}
|
||||||
|
|
||||||
const pasteComponent = (mode, preserveBindings = false) => {
|
const pasteComponent = mode => {
|
||||||
try {
|
try {
|
||||||
// lives in store - also used by drag drop
|
// lives in store - also used by drag drop
|
||||||
store.actions.components.paste(component, mode, preserveBindings)
|
store.actions.components.paste(component, mode)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error saving component")
|
notifications.error("Error saving component")
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ module.exports.processObject = templates.processObject
|
||||||
module.exports.doesContainStrings = templates.doesContainStrings
|
module.exports.doesContainStrings = templates.doesContainStrings
|
||||||
module.exports.doesContainString = templates.doesContainString
|
module.exports.doesContainString = templates.doesContainString
|
||||||
module.exports.disableEscaping = templates.disableEscaping
|
module.exports.disableEscaping = templates.disableEscaping
|
||||||
|
module.exports.findAllBindings = templates.findAllBindings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use vm2 to run JS scripts in a node env
|
* Use vm2 to run JS scripts in a node env
|
||||||
|
|
|
@ -322,3 +322,12 @@ module.exports.doesContainStrings = (template, strings) => {
|
||||||
module.exports.doesContainString = (template, string) => {
|
module.exports.doesContainString = (template, string) => {
|
||||||
return exports.doesContainStrings(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 doesContainStrings = templates.doesContainStrings
|
||||||
export const doesContainString = templates.doesContainString
|
export const doesContainString = templates.doesContainString
|
||||||
export const disableEscaping = templates.disableEscaping
|
export const disableEscaping = templates.disableEscaping
|
||||||
|
export const findAllBindings = templates.findAllBindings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use polyfilled vm to run JS scripts in a browser Env
|
* Use polyfilled vm to run JS scripts in a browser Env
|
||||||
|
|
Loading…
Reference in New Issue