forked from tsai/budibase
Browse Source
* removed binding references to array type * refactored initialiseChildren into seperate file * render function, with code blocks - tested simple cases * few mores tests for control flow * md components - getting TestApp to work * new render wrapper - bug fix * client: providing access to component root elements * code editor working * code editor improvementsgh-pages
committed by
GitHub
22 changed files with 1280 additions and 103 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,35 @@ |
|||||
|
|
||||
|
|
||||
|
const buildCodeForSingleScreen = (screen) => { |
||||
|
let code = ""; |
||||
|
const walkProps = (props) => { |
||||
|
if(props._code && props._code.trim().length > 0) { |
||||
|
code += buildComponentCode(props) |
||||
|
} |
||||
|
|
||||
|
if(!props._children) return; |
||||
|
|
||||
|
for(let child of props._children) { |
||||
|
walkProps(child); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
walkProps(screen.props); |
||||
|
|
||||
|
return code; |
||||
|
} |
||||
|
|
||||
|
export const buildCodeForScreens = screens => { |
||||
|
let allfunctions = ""; |
||||
|
for(let screen of screens) { |
||||
|
allfunctions += buildCodeForSingleScreen(screen); |
||||
|
} |
||||
|
|
||||
|
return (`return ({ ${allfunctions} });`); |
||||
|
} |
||||
|
|
||||
|
const buildComponentCode = (componentProps) => |
||||
|
`"${componentProps._id}" : (render, context) => {
|
||||
|
${componentProps._code} |
||||
|
}, |
||||
|
`;
|
||||
@ -0,0 +1,799 @@ |
|||||
|
import { |
||||
|
hierarchy as hierarchyFunctions, |
||||
|
} from "../../../core/src"; |
||||
|
import { |
||||
|
filter, cloneDeep, sortBy, |
||||
|
map, last, keys, concat, keyBy, |
||||
|
find, isEmpty, values, |
||||
|
} from "lodash/fp"; |
||||
|
import { |
||||
|
pipe, getNode, validate, |
||||
|
constructHierarchy, templateApi |
||||
|
} from "../common/core"; |
||||
|
import { writable } from "svelte/store"; |
||||
|
import { defaultPagesObject } from "../userInterface/pagesParsing/defaultPagesObject" |
||||
|
import { buildPropsHierarchy } from "../userInterface/pagesParsing/buildPropsHierarchy" |
||||
|
import api from "./api"; |
||||
|
import { isRootComponent, getExactComponent } from "../userInterface/pagesParsing/searchComponents"; |
||||
|
import { rename } from "../userInterface/pagesParsing/renameScreen"; |
||||
|
import { |
||||
|
getNewComponentInfo, getScreenInfo, |
||||
|
} from "../userInterface/pagesParsing/createProps"; |
||||
|
import { |
||||
|
loadLibs, loadLibUrls, loadGeneratorLibs |
||||
|
} from "./loadComponentLibraries"; |
||||
|
<<<<<<< HEAD |
||||
|
import { buildCodeForScreens } from "./buildCodeForScreens"; |
||||
|
======= |
||||
|
import { uuid } from './uuid'; |
||||
|
import { generate_screen_css } from './generate_css'; |
||||
|
>>>>>>> master |
||||
|
|
||||
|
let appname = ""; |
||||
|
|
||||
|
export const getStore = () => { |
||||
|
|
||||
|
const initial = { |
||||
|
apps: [], |
||||
|
appname: "", |
||||
|
hierarchy: {}, |
||||
|
actions: [], |
||||
|
triggers: [], |
||||
|
pages: defaultPagesObject(), |
||||
|
mainUi: {}, |
||||
|
unauthenticatedUi: {}, |
||||
|
components: [], |
||||
|
currentFrontEndItem: null, |
||||
|
currentComponentInfo: null, |
||||
|
currentFrontEndType: "none", |
||||
|
currentPageName: "", |
||||
|
currentComponentProps: null, |
||||
|
currentNodeIsNew: false, |
||||
|
errors: [], |
||||
|
activeNav: "database", |
||||
|
isBackend: true, |
||||
|
hasAppPackage: false, |
||||
|
accessLevels: { version: 0, levels: [] }, |
||||
|
currentNode: null, |
||||
|
libraries: null, |
||||
|
showSettings: false, |
||||
|
useAnalytics: true, |
||||
|
}; |
||||
|
|
||||
|
const store = writable(initial); |
||||
|
|
||||
|
store.initialise = initialise(store, initial); |
||||
|
store.newChildRecord = newRecord(store, false); |
||||
|
store.newRootRecord = newRecord(store, true); |
||||
|
store.selectExistingNode = selectExistingNode(store); |
||||
|
store.newChildIndex = newIndex(store, false); |
||||
|
store.newRootIndex = newIndex(store, true); |
||||
|
store.saveCurrentNode = saveCurrentNode(store); |
||||
|
store.importAppDefinition = importAppDefinition(store); |
||||
|
store.deleteCurrentNode = deleteCurrentNode(store); |
||||
|
store.saveField = saveField(store); |
||||
|
store.deleteField = deleteField(store); |
||||
|
store.saveAction = saveAction(store); |
||||
|
store.deleteAction = deleteAction(store); |
||||
|
store.saveTrigger = saveTrigger(store); |
||||
|
store.deleteTrigger = deleteTrigger(store); |
||||
|
store.saveLevel = saveLevel(store); |
||||
|
store.deleteLevel = deleteLevel(store); |
||||
|
store.setActiveNav = setActiveNav(store); |
||||
|
store.saveScreen = saveScreen(store); |
||||
|
store.refreshComponents = refreshComponents(store); |
||||
|
store.addComponentLibrary = addComponentLibrary(store); |
||||
|
store.renameScreen = renameScreen(store); |
||||
|
store.deleteScreen = deleteScreen(store); |
||||
|
store.setCurrentScreen = setCurrentScreen(store); |
||||
|
store.setCurrentPage = setCurrentPage(store); |
||||
|
store.createScreen = createScreen(store); |
||||
|
store.removeComponentLibrary = removeComponentLibrary(store); |
||||
|
store.addStylesheet = addStylesheet(store); |
||||
|
store.removeStylesheet = removeStylesheet(store); |
||||
|
store.savePage = savePage(store); |
||||
|
store.showFrontend = showFrontend(store); |
||||
|
store.showBackend = showBackend(store); |
||||
|
store.showSettings = showSettings(store); |
||||
|
store.useAnalytics = useAnalytics(store); |
||||
|
store.createGeneratedComponents = createGeneratedComponents(store); |
||||
|
store.addChildComponent = addChildComponent(store); |
||||
|
store.selectComponent = selectComponent(store); |
||||
|
store.setComponentProp = setComponentProp(store); |
||||
|
store.setComponentStyle = setComponentStyle(store); |
||||
|
store.setComponentCode = setComponentCode(store); |
||||
|
return store; |
||||
|
} |
||||
|
|
||||
|
export default getStore; |
||||
|
|
||||
|
const initialise = (store, initial) => async () => { |
||||
|
|
||||
|
appname = window.location.hash |
||||
|
? last(window.location.hash.substr(1).split("/")) |
||||
|
: ""; |
||||
|
|
||||
|
if (!appname) { |
||||
|
initial.apps = await api.get(`/_builder/api/apps`).then(r => r.json()); |
||||
|
initial.hasAppPackage = false; |
||||
|
store.set(initial); |
||||
|
return initial; |
||||
|
} |
||||
|
|
||||
|
const pkg = await api.get(`/_builder/api/${appname}/appPackage`) |
||||
|
.then(r => r.json()); |
||||
|
|
||||
|
initial.libraries = await loadLibs(appname, pkg); |
||||
|
initial.generatorLibraries = await loadGeneratorLibs(appname, pkg); |
||||
|
initial.loadLibraryUrls = () => loadLibUrls(appname, pkg); |
||||
|
initial.appname = appname; |
||||
|
initial.pages = pkg.pages; |
||||
|
initial.hasAppPackage = true; |
||||
|
initial.hierarchy = pkg.appDefinition.hierarchy; |
||||
|
initial.accessLevels = pkg.accessLevels; |
||||
|
initial.screens = values(pkg.screens); |
||||
|
initial.generators = generatorsArray(pkg.components.generators); |
||||
|
initial.components = values(pkg.components.components); |
||||
|
initial.actions = values(pkg.appDefinition.actions); |
||||
|
initial.triggers = pkg.appDefinition.triggers; |
||||
|
|
||||
|
if (!!initial.hierarchy && !isEmpty(initial.hierarchy)) { |
||||
|
initial.hierarchy = constructHierarchy(initial.hierarchy); |
||||
|
const shadowHierarchy = createShadowHierarchy(initial.hierarchy); |
||||
|
if (initial.currentNode !== null) |
||||
|
initial.currentNode = getNode( |
||||
|
shadowHierarchy, initial.currentNode.nodeId |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
store.set(initial); |
||||
|
return initial; |
||||
|
} |
||||
|
|
||||
|
const generatorsArray = generators => |
||||
|
pipe(generators, [ |
||||
|
keys, |
||||
|
filter(k => k !== "_lib"), |
||||
|
map(k => generators[k]) |
||||
|
]); |
||||
|
|
||||
|
|
||||
|
const showSettings = store => show => { |
||||
|
store.update(s => { |
||||
|
s.showSettings = !s.showSettings; |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const useAnalytics = store => useAnalytics => { |
||||
|
store.update(s => { |
||||
|
s.useAnalytics = !s.useAnalytics; |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const showBackend = store => () => { |
||||
|
store.update(s => { |
||||
|
s.isBackend = true; |
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const showFrontend = store => () => { |
||||
|
store.update(s => { |
||||
|
s.isBackend = false; |
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const newRecord = (store, useRoot) => () => { |
||||
|
store.update(s => { |
||||
|
s.currentNodeIsNew = true; |
||||
|
const shadowHierarchy = createShadowHierarchy(s.hierarchy); |
||||
|
parent = useRoot ? shadowHierarchy |
||||
|
: getNode( |
||||
|
shadowHierarchy, |
||||
|
s.currentNode.nodeId); |
||||
|
s.errors = []; |
||||
|
s.currentNode = templateApi(shadowHierarchy) |
||||
|
.getNewRecordTemplate(parent, "", true); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
const selectExistingNode = (store) => (nodeId) => { |
||||
|
store.update(s => { |
||||
|
const shadowHierarchy = createShadowHierarchy(s.hierarchy); |
||||
|
s.currentNode = getNode( |
||||
|
shadowHierarchy, nodeId |
||||
|
); |
||||
|
s.currentNodeIsNew = false; |
||||
|
s.errors = []; |
||||
|
s.activeNav = "database"; |
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const newIndex = (store, useRoot) => () => { |
||||
|
store.update(s => { |
||||
|
s.currentNodeIsNew = true; |
||||
|
s.errors = []; |
||||
|
const shadowHierarchy = createShadowHierarchy(s.hierarchy); |
||||
|
parent = useRoot ? shadowHierarchy |
||||
|
: getNode( |
||||
|
shadowHierarchy, |
||||
|
s.currentNode.nodeId); |
||||
|
|
||||
|
s.currentNode = templateApi(shadowHierarchy) |
||||
|
.getNewIndexTemplate(parent); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const saveCurrentNode = (store) => () => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
const errors = validate.node(s.currentNode); |
||||
|
s.errors = errors; |
||||
|
if (errors.length > 0) { |
||||
|
return s; |
||||
|
} |
||||
|
|
||||
|
const parentNode = getNode( |
||||
|
s.hierarchy, s.currentNode.parent().nodeId); |
||||
|
|
||||
|
const existingNode = getNode( |
||||
|
s.hierarchy, s.currentNode.nodeId); |
||||
|
|
||||
|
let index = parentNode.children.length; |
||||
|
if (!!existingNode) { |
||||
|
// remove existing
|
||||
|
index = existingNode.parent().children.indexOf(existingNode); |
||||
|
existingNode.parent().children = pipe(existingNode.parent().children, [ |
||||
|
filter(c => c.nodeId !== existingNode.nodeId) |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
// should add node into existing hierarchy
|
||||
|
const cloned = cloneDeep(s.currentNode); |
||||
|
templateApi(s.hierarchy).constructNode( |
||||
|
parentNode, |
||||
|
cloned |
||||
|
); |
||||
|
|
||||
|
const newIndexOfchild = child => { |
||||
|
if (child === cloned) return index; |
||||
|
const currentIndex = parentNode.children.indexOf(child); |
||||
|
return currentIndex >= index ? currentIndex + 1 : currentIndex; |
||||
|
} |
||||
|
|
||||
|
parentNode.children = pipe(parentNode.children, [ |
||||
|
sortBy(newIndexOfchild) |
||||
|
]); |
||||
|
|
||||
|
if (!existingNode && s.currentNode.type === "record") { |
||||
|
const defaultIndex = templateApi(s.hierarchy) |
||||
|
.getNewIndexTemplate(cloned.parent()); |
||||
|
defaultIndex.name = `all_${cloned.collectionName}`; |
||||
|
defaultIndex.allowedRecordNodeIds = [cloned.nodeId]; |
||||
|
} |
||||
|
|
||||
|
s.currentNodeIsNew = false; |
||||
|
|
||||
|
savePackage(store, s); |
||||
|
|
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const importAppDefinition = store => appDefinition => { |
||||
|
store.update(s => { |
||||
|
s.hierarchy = appDefinition.hierarchy; |
||||
|
s.currentNode = appDefinition.hierarchy.children.length > 0 |
||||
|
? appDefinition.hierarchy.children[0] |
||||
|
: null; |
||||
|
s.actions = appDefinition.actions; |
||||
|
s.triggers = appDefinition.triggers; |
||||
|
s.currentNodeIsNew = false; |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const deleteCurrentNode = store => () => { |
||||
|
store.update(s => { |
||||
|
const nodeToDelete = getNode(s.hierarchy, s.currentNode.nodeId); |
||||
|
s.currentNode = hierarchyFunctions.isRoot(nodeToDelete.parent()) |
||||
|
? find(n => n != s.currentNode) |
||||
|
(s.hierarchy.children) |
||||
|
: nodeToDelete.parent(); |
||||
|
if (hierarchyFunctions.isRecord(nodeToDelete)) { |
||||
|
nodeToDelete.parent().children = filter(c => c.nodeId !== nodeToDelete.nodeId) |
||||
|
(nodeToDelete.parent().children); |
||||
|
} else { |
||||
|
nodeToDelete.parent().indexes = filter(c => c.nodeId !== nodeToDelete.nodeId) |
||||
|
(nodeToDelete.parent().indexes); |
||||
|
} |
||||
|
s.errors = []; |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const saveField = databaseStore => (field) => { |
||||
|
databaseStore.update(db => { |
||||
|
db.currentNode.fields = filter(f => f.name !== field.name) |
||||
|
(db.currentNode.fields); |
||||
|
|
||||
|
templateApi(db.hierarchy).addField(db.currentNode, field); |
||||
|
return db; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
const deleteField = databaseStore => field => { |
||||
|
databaseStore.update(db => { |
||||
|
db.currentNode.fields = filter(f => f.name !== field.name) |
||||
|
(db.currentNode.fields); |
||||
|
|
||||
|
return db; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
const saveAction = store => (newAction, isNew, oldAction = null) => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
const existingAction = isNew |
||||
|
? null |
||||
|
: find(a => a.name === oldAction.name)(s.actions); |
||||
|
|
||||
|
if (existingAction) { |
||||
|
s.actions = pipe(s.actions, [ |
||||
|
map(a => a === existingAction ? newAction : a) |
||||
|
]); |
||||
|
} else { |
||||
|
s.actions.push(newAction); |
||||
|
} |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const deleteAction = store => action => { |
||||
|
store.update(s => { |
||||
|
s.actions = filter(a => a.name !== action.name)(s.actions); |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const saveTrigger = store => (newTrigger, isNew, oldTrigger = null) => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
const existingTrigger = isNew |
||||
|
? null |
||||
|
: find(a => a.name === oldTrigger.name)(s.triggers); |
||||
|
|
||||
|
if (existingTrigger) { |
||||
|
s.triggers = pipe(s.triggers, [ |
||||
|
map(a => a === existingTrigger ? newTrigger : a) |
||||
|
]); |
||||
|
} else { |
||||
|
s.triggers.push(newTrigger); |
||||
|
} |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const deleteTrigger = store => trigger => { |
||||
|
store.update(s => { |
||||
|
s.triggers = filter(t => t.name !== trigger.name)(s.triggers); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const incrementAccessLevelsVersion = (s) => |
||||
|
s.accessLevels.version = (s.accessLevels.version || 0) + 1; |
||||
|
|
||||
|
const saveLevel = store => (newLevel, isNew, oldLevel = null) => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
const levels = s.accessLevels.levels; |
||||
|
|
||||
|
const existingLevel = isNew |
||||
|
? null |
||||
|
: find(a => a.name === oldLevel.name)(levels); |
||||
|
|
||||
|
if (existingLevel) { |
||||
|
s.accessLevels.levels = pipe(levels, [ |
||||
|
map(a => a === existingLevel ? newLevel : a) |
||||
|
]); |
||||
|
} else { |
||||
|
s.accessLevels.levels.push(newLevel); |
||||
|
} |
||||
|
|
||||
|
incrementAccessLevelsVersion(s); |
||||
|
|
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const deleteLevel = store => level => { |
||||
|
store.update(s => { |
||||
|
s.accessLevels.levels = filter(t => t.name !== level.name)(s.accessLevels.levels); |
||||
|
incrementAccessLevelsVersion(s); |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const setActiveNav = store => navName => { |
||||
|
store.update(s => { |
||||
|
s.activeNav = navName; |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const createShadowHierarchy = hierarchy => |
||||
|
constructHierarchy(JSON.parse(JSON.stringify(hierarchy))); |
||||
|
|
||||
|
const saveScreen = store => (screen) => { |
||||
|
store.update(s => { |
||||
|
return _saveScreen(store, s, screen); |
||||
|
}) |
||||
|
}; |
||||
|
|
||||
|
const _saveScreen = (store, s, screen) => { |
||||
|
const screens = pipe(s.screens, [ |
||||
|
filter(c => c.name !== screen.name), |
||||
|
concat([screen]) |
||||
|
]); |
||||
|
|
||||
|
s.screens = screens; |
||||
|
s.currentFrontEndItem = screen; |
||||
|
s.currentComponentInfo = getScreenInfo( |
||||
|
s.components, screen); |
||||
|
|
||||
|
api.post(`/_builder/api/${s.appname}/screen`, screen) |
||||
|
.then(() => savePackage(store, s)); |
||||
|
|
||||
|
return s; |
||||
|
} |
||||
|
|
||||
|
const _save = (appname, screen, store, s) => |
||||
|
api.post(`/_builder/api/${appname}/screen`, screen) |
||||
|
.then(() => savePackage(store, s)); |
||||
|
|
||||
|
const createScreen = store => (screenName, layoutComponentName) => { |
||||
|
store.update(s => { |
||||
|
const newComponentInfo = getNewComponentInfo( |
||||
|
s.components, layoutComponentName, screenName); |
||||
|
|
||||
|
s.currentFrontEndItem = newComponentInfo.component; |
||||
|
s.currentComponentInfo = newComponentInfo; |
||||
|
s.currentFrontEndType = "screen"; |
||||
|
|
||||
|
return _saveScreen(store, s, newComponentInfo.component); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const createGeneratedComponents = store => components => { |
||||
|
store.update(s => { |
||||
|
s.components = [...s.components, ...components]; |
||||
|
s.screens = [...s.screens, ...components]; |
||||
|
|
||||
|
const doCreate = async () => { |
||||
|
for (let c of components) { |
||||
|
await api.post(`/_builder/api/${s.appname}/screen`, c); |
||||
|
} |
||||
|
|
||||
|
await savePackage(store, s); |
||||
|
} |
||||
|
|
||||
|
doCreate(); |
||||
|
|
||||
|
return s; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const deleteScreen = store => name => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
const components = pipe(s.components, [ |
||||
|
filter(c => c.name !== name) |
||||
|
]); |
||||
|
|
||||
|
const screens = pipe(s.screens, [ |
||||
|
filter(c => c.name !== name) |
||||
|
]); |
||||
|
|
||||
|
s.components = components; |
||||
|
s.screens = screens; |
||||
|
if (s.currentFrontEndItem.name === name) { |
||||
|
s.currentFrontEndItem = null; |
||||
|
s.currentFrontEndType = ""; |
||||
|
} |
||||
|
|
||||
|
api.delete(`/_builder/api/${s.appname}/screen/${name}`); |
||||
|
|
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const renameScreen = store => (oldname, newname) => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
const { |
||||
|
screens, pages, error, changedScreens |
||||
|
} = rename(s.pages, s.screens, oldname, newname); |
||||
|
|
||||
|
if (error) { |
||||
|
// should really do something with this
|
||||
|
return s; |
||||
|
} |
||||
|
|
||||
|
s.screens = screens; |
||||
|
s.pages = pages; |
||||
|
if (s.currentFrontEndItem.name === oldname) |
||||
|
s.currentFrontEndItem.name = newname; |
||||
|
|
||||
|
const saveAllChanged = async () => { |
||||
|
for (let screenName of changedScreens) { |
||||
|
const changedScreen |
||||
|
= getExactComponent(screens, screenName); |
||||
|
await api.post(`/_builder/api/${s.appname}/screen`, changedScreen); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
api.patch(`/_builder/api/${s.appname}/screen`, { |
||||
|
oldname, newname |
||||
|
}) |
||||
|
.then(() => saveAllChanged()) |
||||
|
.then(() => { |
||||
|
savePackage(store, s); |
||||
|
}); |
||||
|
|
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const savePage = store => async page => { |
||||
|
store.update(s => { |
||||
|
if (s.currentFrontEndType !== "page" || !s.currentPageName) { |
||||
|
return s; |
||||
|
} |
||||
|
|
||||
|
s.pages[s.currentPageName] = page; |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const addComponentLibrary = store => async lib => { |
||||
|
|
||||
|
const response = |
||||
|
await api.get(`/_builder/api/${appname}/componentlibrary?lib=${encodeURI(lib)}`, undefined, false); |
||||
|
|
||||
|
const success = response.status === 200; |
||||
|
|
||||
|
const error = response.status === 404 |
||||
|
? `Could not find library ${lib}` |
||||
|
: success |
||||
|
? "" |
||||
|
: response.statusText; |
||||
|
|
||||
|
const components = success |
||||
|
? await response.json() |
||||
|
: []; |
||||
|
|
||||
|
store.update(s => { |
||||
|
if (success) { |
||||
|
|
||||
|
const componentsArray = []; |
||||
|
for (let c in components) { |
||||
|
componentsArray.push(components[c]); |
||||
|
} |
||||
|
|
||||
|
s.components = pipe(s.components, [ |
||||
|
filter(c => !c.name.startsWith(`${lib}/`)), |
||||
|
concat(componentsArray) |
||||
|
]); |
||||
|
|
||||
|
s.pages.componentLibraries.push(lib); |
||||
|
savePackage(store, s); |
||||
|
} |
||||
|
|
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const removeComponentLibrary = store => lib => { |
||||
|
store.update(s => { |
||||
|
|
||||
|
|
||||
|
s.pages.componentLibraries = filter(l => l !== lib)( |
||||
|
s.pages.componentLibraries); |
||||
|
savePackage(store, s); |
||||
|
|
||||
|
|
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const addStylesheet = store => stylesheet => { |
||||
|
store.update(s => { |
||||
|
s.pages.stylesheets.push(stylesheet); |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const removeStylesheet = store => stylesheet => { |
||||
|
store.update(s => { |
||||
|
s.pages.stylesheets = filter(s => s !== stylesheet)(s.pages.stylesheets); |
||||
|
savePackage(store, s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const refreshComponents = store => async () => { |
||||
|
|
||||
|
const componentsAndGenerators = |
||||
|
await api.get(`/_builder/api/${db.appname}/components`).then(r => r.json()); |
||||
|
|
||||
|
const components = pipe(componentsAndGenerators.components, [ |
||||
|
keys, |
||||
|
map(k => ({ ...componentsAndGenerators[k], name: k })) |
||||
|
]); |
||||
|
|
||||
|
store.update(s => { |
||||
|
s.components = pipe(s.components, [ |
||||
|
filter(c => !isRootComponent(c)), |
||||
|
concat(components) |
||||
|
]); |
||||
|
s.generators = componentsAndGenerators.generators; |
||||
|
return s; |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const savePackage = (store, s) => { |
||||
|
|
||||
|
const appDefinition = { |
||||
|
hierarchy: s.hierarchy, |
||||
|
triggers: s.triggers, |
||||
|
actions: keyBy("name")(s.actions), |
||||
|
props: { |
||||
|
main: buildPropsHierarchy( |
||||
|
s.components, |
||||
|
s.screens, |
||||
|
s.pages.main.appBody), |
||||
|
unauthenticated: buildPropsHierarchy( |
||||
|
s.components, |
||||
|
s.screens, |
||||
|
s.pages.unauthenticated.appBody) |
||||
|
}, |
||||
|
uiFunctions: buildCodeForScreens(s.screens) |
||||
|
}; |
||||
|
|
||||
|
const data = { |
||||
|
appDefinition, |
||||
|
accessLevels: s.accessLevels, |
||||
|
pages: s.pages, |
||||
|
} |
||||
|
|
||||
|
return api.post(`/_builder/api/${s.appname}/appPackage`, data); |
||||
|
} |
||||
|
|
||||
|
const setCurrentScreen = store => screenName => { |
||||
|
store.update(s => { |
||||
|
const screen = getExactComponent(s.screens, screenName); |
||||
|
s.currentFrontEndItem = screen; |
||||
|
s.currentFrontEndType = "screen"; |
||||
|
s.currentComponentInfo = getScreenInfo(s.components, screen); |
||||
|
setCurrentScreenFunctions(s); |
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const setCurrentPage = store => pageName => { |
||||
|
store.update(s => { |
||||
|
s.currentFrontEndType = "page"; |
||||
|
s.currentPageName = pageName; |
||||
|
setCurrentScreenFunctions(s); |
||||
|
return s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
const addChildComponent = store => component => { |
||||
|
|
||||
|
store.update(s => { |
||||
|
const newComponent = getNewComponentInfo( |
||||
|
s.components, component); |
||||
|
|
||||
|
let children = s.currentComponentInfo.component ? |
||||
|
s.currentComponentInfo.component.props._children : |
||||
|
s.currentComponentInfo._children; |
||||
|
|
||||
|
const component_definition = Object.assign( |
||||
|
cloneDeep(newComponent.fullProps), { |
||||
|
_component: component, |
||||
|
_styles: { position: {}, layout: {} }, |
||||
|
_id: uuid() |
||||
|
}) |
||||
|
|
||||
|
if (children) { |
||||
|
if (s.currentComponentInfo.component) { |
||||
|
s.currentComponentInfo.component.props._children = children.concat(component_definition); |
||||
|
} else { |
||||
|
s.currentComponentInfo._children = children.concat(component_definition) |
||||
|
} |
||||
|
} else { |
||||
|
if (s.currentComponentInfo.component) { |
||||
|
s.currentComponentInfo.component.props._children = [component_definition]; |
||||
|
} else { |
||||
|
s.currentComponentInfo._children = [component_definition] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_saveScreen(store, s, s.currentFrontEndItem); |
||||
|
|
||||
|
_saveScreen(store, s, s.currentFrontEndItem); |
||||
|
|
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const selectComponent = store => component => { |
||||
|
store.update(s => { |
||||
|
s.currentComponentInfo = component; |
||||
|
return s; |
||||
|
}) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
const setComponentProp = store => (name, value) => { |
||||
|
store.update(s => { |
||||
|
const current_component = s.currentComponentInfo; |
||||
|
s.currentComponentInfo[name] = value; |
||||
|
_saveScreen(store, s, s.currentFrontEndItem); |
||||
|
s.currentComponentInfo = current_component; |
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const setComponentStyle = store => (type, name, value) => { |
||||
|
store.update(s => { |
||||
|
if (!s.currentComponentInfo._styles) { |
||||
|
s.currentComponentInfo._styles = {}; |
||||
|
} |
||||
|
s.currentComponentInfo._styles[type][name] = value; |
||||
|
s.currentFrontEndItem._css = generate_screen_css(s.currentFrontEndItem.props._children) |
||||
|
|
||||
|
// save without messing with the store
|
||||
|
_save(s.appname, s.currentFrontEndItem, store, s) |
||||
|
|
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const setComponentCode = store => (code) => { |
||||
|
store.update(s => { |
||||
|
s.currentComponentInfo._code = code; |
||||
|
|
||||
|
setCurrentScreenFunctions(s); |
||||
|
// save without messing with the store
|
||||
|
_save(s.appname, s.currentFrontEndItem, store, s) |
||||
|
|
||||
|
return s; |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const setCurrentScreenFunctions = (s) => { |
||||
|
s.currentScreenFunctions = |
||||
|
s.currentFrontEndItem === "screen" |
||||
|
? buildCodeForScreens([s.currentFrontEndItem]) |
||||
|
: "({});"; |
||||
|
} |
||||
|
After Width: | Height: | Size: 158 B |
@ -0,0 +1,12 @@ |
|||||
|
export { default as LayoutIcon } from './Layout.svelte'; |
||||
|
export { default as PaintIcon } from './Paint.svelte'; |
||||
|
export { default as TerminalIcon } from './Terminal.svelte'; |
||||
|
export { default as InputIcon } from './Input.svelte'; |
||||
|
export { default as ImageIcon } from './Image.svelte'; |
||||
|
export { default as ArrowDownIcon } from './ArrowDown.svelte'; |
||||
|
<<<<<<< HEAD |
||||
|
export { default as CircleIndicator } from './CircleIndicator.svelte'; |
||||
|
======= |
||||
|
export { default as EventsIcon } from './Events.svelte'; |
||||
|
export { default as PencilIcon } from './Pencil.svelte'; |
||||
|
>>>>>>> master |
||||
@ -0,0 +1,149 @@ |
|||||
|
<script> |
||||
|
import PropsView from "./PropsView.svelte"; |
||||
|
import { store } from "../builderStore"; |
||||
|
import IconButton from "../common/IconButton.svelte"; |
||||
|
<<<<<<< HEAD |
||||
|
import { LayoutIcon, PaintIcon, TerminalIcon, CircleIndicator } from '../common/Icons/'; |
||||
|
======= |
||||
|
import { LayoutIcon, PaintIcon, TerminalIcon, EventsIcon } from '../common/Icons/'; |
||||
|
>>>>>>> master |
||||
|
import CodeEditor from './CodeEditor.svelte'; |
||||
|
import LayoutEditor from './LayoutEditor.svelte'; |
||||
|
import EventsEditor from "./EventsEditor"; |
||||
|
|
||||
|
let current_view = 'props'; |
||||
|
let codeEditor; |
||||
|
|
||||
|
$: component = $store.currentComponentInfo; |
||||
|
$: originalName = component.name; |
||||
|
$: name = component.name; |
||||
|
$: description = component.description; |
||||
|
$: componentInfo = $store.currentComponentInfo; |
||||
|
$: components = $store.components; |
||||
|
|
||||
|
const onPropChanged = store.setComponentProp; |
||||
|
const onStyleChanged = store.setComponentStyle; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
<ul> |
||||
|
<li> |
||||
|
<button class:selected={current_view === 'props'} on:click={() => current_view = 'props'}> |
||||
|
<PaintIcon /> |
||||
|
</button> |
||||
|
</li> |
||||
|
<li> |
||||
|
<button class:selected={current_view === 'layout'} on:click={() => current_view = 'layout'}> |
||||
|
<LayoutIcon /> |
||||
|
</button> |
||||
|
</li> |
||||
|
<li> |
||||
|
<button class:selected={current_view === 'code'} on:click={() => codeEditor && codeEditor.show()}> |
||||
|
{#if componentInfo._code && componentInfo._code.trim().length > 0} |
||||
|
<div class="button-indicator"> |
||||
|
<CircleIndicator /> |
||||
|
</div> |
||||
|
{/if} |
||||
|
<TerminalIcon /> |
||||
|
</button> |
||||
|
</li> |
||||
|
<li> |
||||
|
<button class:selected={current_view === 'events'} on:click={() => current_view = 'events'}> |
||||
|
<EventsIcon /> |
||||
|
</button> |
||||
|
</li> |
||||
|
</ul> |
||||
|
|
||||
|
{#if !componentInfo.component} |
||||
|
<div class="component-props-container"> |
||||
|
|
||||
|
{#if current_view === 'props'} |
||||
|
<PropsView {componentInfo} {components} {onPropChanged} /> |
||||
|
{:else if current_view === 'layout'} |
||||
|
<LayoutEditor {onStyleChanged} {componentInfo}/> |
||||
|
<<<<<<< HEAD |
||||
|
======= |
||||
|
{:else if current_view === 'events'} |
||||
|
<EventsEditor {componentInfo} {components} {onPropChanged} /> |
||||
|
{:else} |
||||
|
<CodeEditor /> |
||||
|
>>>>>>> master |
||||
|
{/if} |
||||
|
|
||||
|
<CodeEditor |
||||
|
bind:this={codeEditor} |
||||
|
code={$store.currentComponentInfo._code} |
||||
|
onCodeChanged={store.setComponentCode} /> |
||||
|
|
||||
|
</div> |
||||
|
{:else} |
||||
|
<h1> This is a screen, this will be dealt with later</h1> |
||||
|
{/if} |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.title > div:nth-child(1) { |
||||
|
grid-column-start: name; |
||||
|
color: var(--secondary100); |
||||
|
} |
||||
|
|
||||
|
.title > div:nth-child(2) { |
||||
|
grid-column-start: actions; |
||||
|
} |
||||
|
|
||||
|
.component-props-container { |
||||
|
margin-top: 10px; |
||||
|
flex: 1 1 auto; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
ul { |
||||
|
list-style: none; |
||||
|
display: flex; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
margin-right: 20px; |
||||
|
background: none; |
||||
|
border-radius: 5px; |
||||
|
width: 48px; |
||||
|
height: 48px; |
||||
|
} |
||||
|
|
||||
|
li button { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background: none; |
||||
|
border: none; |
||||
|
border-radius: 5px; |
||||
|
padding: 12px; |
||||
|
outline: none; |
||||
|
cursor: pointer; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.selected { |
||||
|
color: var(--button-text); |
||||
|
background: var(--background-button)!important; |
||||
|
} |
||||
|
|
||||
|
.button-indicator { |
||||
|
position: absolute; |
||||
|
top: 6px; |
||||
|
right: 10px; |
||||
|
color: var(--button-text); |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,74 @@ |
|||||
|
<script> |
||||
|
import { some, includes, filter } from "lodash/fp"; |
||||
|
import Textbox from "../common/Textbox.svelte"; |
||||
|
import Dropdown from "../common/Dropdown.svelte"; |
||||
|
import PropControl from "./PropControl.svelte"; |
||||
|
import IconButton from "../common/IconButton.svelte"; |
||||
|
|
||||
|
export let componentInfo; |
||||
|
export let onPropChanged = () => {}; |
||||
|
export let components; |
||||
|
|
||||
|
let errors = []; |
||||
|
let props = {}; |
||||
|
|
||||
|
<<<<<<< HEAD |
||||
|
const props_to_ignore = ['_component','_children', '_layout', '_code', '_id']; |
||||
|
======= |
||||
|
const props_to_ignore = ['_component','_children', '_styles', '_id']; |
||||
|
>>>>>>> master |
||||
|
|
||||
|
$: propDefs = componentInfo && Object.entries(componentInfo).filter(([name])=> !props_to_ignore.includes(name)); |
||||
|
|
||||
|
function find_type(prop_name) { |
||||
|
if(!componentInfo._component) return; |
||||
|
return components.find(({name}) => name === componentInfo._component).props[prop_name]; |
||||
|
} |
||||
|
|
||||
|
let setProp = (name, value) => { |
||||
|
onPropChanged(name, value); |
||||
|
} |
||||
|
|
||||
|
const fieldHasError = (propName) => |
||||
|
some(e => e.propName === propName)(errors); |
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
|
||||
|
<form class="uk-form-stacked form-root"> |
||||
|
{#each propDefs as [prop_name, prop_value], index} |
||||
|
|
||||
|
<div class="prop-container"> |
||||
|
|
||||
|
<PropControl {setProp} |
||||
|
{prop_name} |
||||
|
{prop_value} |
||||
|
prop_type={find_type(prop_name)} |
||||
|
{index} |
||||
|
disabled={false} /> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
{/each} |
||||
|
|
||||
|
</form> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
.root { |
||||
|
font-size:10pt; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.form-root { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
} |
||||
|
|
||||
|
.prop-container { |
||||
|
flex: 1 1 auto; |
||||
|
min-width: 250px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,64 @@ |
|||||
|
import { buildCodeForScreens } from "../src/builderStore/buildCodeForScreens"; |
||||
|
|
||||
|
describe("buildCodeForScreen",() => { |
||||
|
|
||||
|
it("should package _code into runnable function, for simple screen props", () => { |
||||
|
const screen = { |
||||
|
props: { |
||||
|
_id: "1234", |
||||
|
_code: "render('render argument');" |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
let renderArg; |
||||
|
const render = (arg) => { |
||||
|
renderArg = arg; |
||||
|
} |
||||
|
const uiFunctions = getFunctions(screen); |
||||
|
|
||||
|
const targetfunction = uiFunctions[screen.props._id]; |
||||
|
expect(targetfunction).toBeDefined(); |
||||
|
|
||||
|
targetfunction(render); |
||||
|
|
||||
|
expect(renderArg).toBe("render argument"); |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
it("should package _code into runnable function, for _children ", () => { |
||||
|
const screen = { |
||||
|
props: { |
||||
|
_id: "parent", |
||||
|
_code: "render('parent argument');", |
||||
|
_children: [ |
||||
|
{ |
||||
|
_id: "child1", |
||||
|
_code: "render('child 1 argument');" |
||||
|
}, |
||||
|
{ |
||||
|
_id: "child2", |
||||
|
_code: "render('child 2 argument');" |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
let renderArg; |
||||
|
const render = (arg) => { |
||||
|
renderArg = arg; |
||||
|
} |
||||
|
const uiFunctions = getFunctions(screen); |
||||
|
|
||||
|
const targetfunction = uiFunctions["child2"]; |
||||
|
expect(targetfunction).toBeDefined(); |
||||
|
|
||||
|
targetfunction(render); |
||||
|
|
||||
|
expect(renderArg).toBe("child 2 argument"); |
||||
|
|
||||
|
}) |
||||
|
|
||||
|
}); |
||||
|
|
||||
|
const getFunctions = (screen) => |
||||
|
new Function(buildCodeForScreens([screen]))(); |
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue