Merge branch 'master' of github.com:Budibase/budibase
This commit is contained in:
commit
e25cab15e2
File diff suppressed because one or more lines are too long
|
@ -36,6 +36,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/client": "^0.0.16",
|
"@budibase/client": "^0.0.16",
|
||||||
"@nx-js/compiler-util": "^2.0.0",
|
"@nx-js/compiler-util": "^2.0.0",
|
||||||
|
"codemirror": "^5.51.0",
|
||||||
"date-fns": "^1.29.0",
|
"date-fns": "^1.29.0",
|
||||||
"feather-icons": "^4.21.0",
|
"feather-icons": "^4.21.0",
|
||||||
"flatpickr": "^4.5.7",
|
"flatpickr": "^4.5.7",
|
||||||
|
|
|
@ -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}
|
||||||
|
},
|
||||||
|
`;
|
|
@ -22,6 +22,7 @@ import {
|
||||||
import {
|
import {
|
||||||
loadLibs, loadLibUrls, loadGeneratorLibs
|
loadLibs, loadLibUrls, loadGeneratorLibs
|
||||||
} from "./loadComponentLibraries";
|
} from "./loadComponentLibraries";
|
||||||
|
import { buildCodeForScreens } from "./buildCodeForScreens";
|
||||||
import { uuid } from './uuid';
|
import { uuid } from './uuid';
|
||||||
import { generate_screen_css } from './generate_css';
|
import { generate_screen_css } from './generate_css';
|
||||||
|
|
||||||
|
@ -97,6 +98,7 @@ export const getStore = () => {
|
||||||
store.selectComponent = selectComponent(store);
|
store.selectComponent = selectComponent(store);
|
||||||
store.setComponentProp = setComponentProp(store);
|
store.setComponentProp = setComponentProp(store);
|
||||||
store.setComponentStyle = setComponentStyle(store);
|
store.setComponentStyle = setComponentStyle(store);
|
||||||
|
store.setComponentCode = setComponentCode(store);
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -669,7 +671,8 @@ const savePackage = (store, s) => {
|
||||||
s.components,
|
s.components,
|
||||||
s.screens,
|
s.screens,
|
||||||
s.pages.unauthenticated.appBody)
|
s.pages.unauthenticated.appBody)
|
||||||
}
|
},
|
||||||
|
uiFunctions: buildCodeForScreens(s.screens)
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
|
@ -687,6 +690,7 @@ const setCurrentScreen = store => screenName => {
|
||||||
s.currentFrontEndItem = screen;
|
s.currentFrontEndItem = screen;
|
||||||
s.currentFrontEndType = "screen";
|
s.currentFrontEndType = "screen";
|
||||||
s.currentComponentInfo = getScreenInfo(s.components, screen);
|
s.currentComponentInfo = getScreenInfo(s.components, screen);
|
||||||
|
setCurrentScreenFunctions(s);
|
||||||
return s;
|
return s;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -695,8 +699,9 @@ const setCurrentPage = store => pageName => {
|
||||||
store.update(s => {
|
store.update(s => {
|
||||||
s.currentFrontEndType = "page";
|
s.currentFrontEndType = "page";
|
||||||
s.currentPageName = pageName;
|
s.currentPageName = pageName;
|
||||||
|
setCurrentScreenFunctions(s);
|
||||||
return s;
|
return s;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const addChildComponent = store => component => {
|
const addChildComponent = store => component => {
|
||||||
|
@ -770,3 +775,22 @@ const setComponentStyle = store => (type, name, value) => {
|
||||||
return 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])
|
||||||
|
: "({});";
|
||||||
|
}
|
||||||
|
|
|
@ -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])
|
||||||
|
: "({});";
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8" width="8" height="8">
|
||||||
|
<circle cx="4" cy="4" r="4" stroke-width="0" fill="currentColor" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 158 B |
|
@ -4,5 +4,6 @@ export { default as TerminalIcon } from './Terminal.svelte';
|
||||||
export { default as InputIcon } from './Input.svelte';
|
export { default as InputIcon } from './Input.svelte';
|
||||||
export { default as ImageIcon } from './Image.svelte';
|
export { default as ImageIcon } from './Image.svelte';
|
||||||
export { default as ArrowDownIcon } from './ArrowDown.svelte';
|
export { default as ArrowDownIcon } from './ArrowDown.svelte';
|
||||||
export { default as EventsIcon } from './Events.svelte';
|
export { default as CircleIndicator } from './CircleIndicator.svelte';
|
||||||
export { default as PencilIcon } from './Pencil.svelte';
|
export { default as PencilIcon } from './Pencil.svelte';
|
||||||
|
export { default as EventsIcon } from './Events.svelte';
|
|
@ -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
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
<link rel='icon' type='image/png' href='/_builder/favicon.png'>
|
<link rel='icon' type='image/png' href='/_builder/favicon.png'>
|
||||||
<link rel='stylesheet' href='/_builder/global.css'>
|
<link rel='stylesheet' href='/_builder/global.css'>
|
||||||
|
<link rel='stylesheet' href='/_builder/codemirror.css'>
|
||||||
|
<link rel='stylesheet' href='/_builder/monokai.css'>
|
||||||
<link rel='stylesheet' href='/_builder/bundle.css'>
|
<link rel='stylesheet' href='/_builder/bundle.css'>
|
||||||
<link rel='stylesheet' href='/_builder/fonts.css'>
|
<link rel='stylesheet' href='/_builder/fonts.css'>
|
||||||
<link rel='stylesheet' href="/_builder/uikit.min.css">
|
<link rel='stylesheet' href="/_builder/uikit.min.css">
|
||||||
|
|
|
@ -10,6 +10,8 @@ import "/assets/budibase-logo.png";
|
||||||
import "/assets/budibase-logo-only.png";
|
import "/assets/budibase-logo-only.png";
|
||||||
import "uikit/dist/css/uikit.min.css";
|
import "uikit/dist/css/uikit.min.css";
|
||||||
import "uikit/dist/js/uikit.min.js";
|
import "uikit/dist/js/uikit.min.js";
|
||||||
|
import "codemirror/lib/codemirror.css";
|
||||||
|
import 'codemirror/theme/monokai.css';
|
||||||
|
|
||||||
const app = new App({
|
const app = new App({
|
||||||
target: document.getElementById("app")
|
target: document.getElementById("app")
|
||||||
|
|
|
@ -1,47 +1,83 @@
|
||||||
<script>
|
<script>
|
||||||
let snippets = [];
|
import { store } from "../builderStore/store";
|
||||||
let current_snippet = 0;
|
import UIkit from "uikit";
|
||||||
let snippet_text = ''
|
import Button from "../common/Button.svelte";
|
||||||
let id = 0;
|
import ButtonGroup from "../common/ButtonGroup.svelte";
|
||||||
|
import CodeMirror from "codemirror";
|
||||||
|
import "codemirror/mode/javascript/javascript.js";
|
||||||
|
|
||||||
function save_snippet() {
|
export let onCodeChanged;
|
||||||
if (!snippet_text) return;
|
export let code;
|
||||||
|
|
||||||
const index = snippets.findIndex(({ id }) => current_snippet === id);
|
export const show = () => {
|
||||||
|
UIkit.modal(codeModal).show();
|
||||||
|
}
|
||||||
|
|
||||||
if (index > -1) {
|
let codeModal;
|
||||||
snippets[index].snippet = snippet_text;
|
let editor;
|
||||||
} else {
|
let cmInstance;
|
||||||
snippets = snippets.concat({ snippet: snippet_text , id: id });
|
|
||||||
|
$: currentCode = code;
|
||||||
|
$: originalCode = code;
|
||||||
|
$: {
|
||||||
|
if(editor) {
|
||||||
|
if(!cmInstance) {
|
||||||
|
cmInstance = CodeMirror.fromTextArea(editor, {
|
||||||
|
mode: 'javascript',
|
||||||
|
lineNumbers: false,
|
||||||
|
lineWrapping: true,
|
||||||
|
smartIndent: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
readOnly: false
|
||||||
|
});
|
||||||
|
cmInstance.on("change", () => currentCode = cmInstance.getValue());
|
||||||
}
|
}
|
||||||
snippet_text = '';
|
cmInstance.focus();
|
||||||
current_snippet = ++id;
|
cmInstance.setValue(code);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
UIkit.modal(codeModal).hide();
|
||||||
|
currentCode = originalCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
originalCode = currentCode;
|
||||||
|
onCodeChanged(currentCode);
|
||||||
|
UIkit.modal(codeModal).hide();
|
||||||
|
}
|
||||||
|
|
||||||
function edit_snippet(id) {
|
|
||||||
const { snippet, id: _id } = snippets.find(({ id:_id }) => _id === id);
|
|
||||||
current_snippet = id
|
|
||||||
snippet_text = snippet;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h3>Code</h3>
|
|
||||||
|
|
||||||
<p>Use the code box below to add snippets of javascript to enhance your webapp</p>
|
<div bind:this={codeModal} uk-modal>
|
||||||
|
<div class="uk-modal-dialog" uk-overflow-auto>
|
||||||
|
|
||||||
<div class="editor">
|
<div class="uk-modal-header">
|
||||||
<textarea class="code" bind:value={snippet_text} />
|
<h3>Code</h3>
|
||||||
<button on:click={save_snippet}>Save</button>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="snippets">
|
<div class="uk-modal-body uk-form-horizontal" >
|
||||||
<h3>Snippets added</h3>
|
|
||||||
{#each snippets as { id, snippet } }
|
<p>Use the code box below to control how this component is displayed, with javascript.</p>
|
||||||
<div class="snippet">
|
|
||||||
<pre class="code">{snippet}</pre>
|
<div>
|
||||||
<button on:click={() => edit_snippet(id)}>Edit</button>
|
<div class="editor-code-surround">function(render, context, store) {"{"}</div>
|
||||||
</div>
|
<div class="editor">
|
||||||
{/each}
|
<textarea bind:this={editor}></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="editor-code-surround">
|
||||||
|
{"}"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ButtonGroup style="float: right;">
|
||||||
|
<Button color="primary" grouped on:click={save}>Save</Button>
|
||||||
|
<Button color="tertiary" grouped on:click={cancel}>Close</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -60,60 +96,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
position: relative;
|
border-style: dotted;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: gainsboro;
|
||||||
|
padding: 10px 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code {
|
.editor-code-surround {
|
||||||
width: 100%;
|
font-family: "Courier New", Courier, monospace;
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
background: #173157;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
white-space: pre;
|
|
||||||
color: #eee;
|
|
||||||
padding: 10px;
|
|
||||||
font-family: monospace;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor textarea {
|
|
||||||
resize: none;
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
position: absolute;
|
|
||||||
box-shadow: 0 0 black;
|
|
||||||
color: #eee;
|
|
||||||
right: 5px;
|
|
||||||
bottom: 10px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 9px;
|
|
||||||
font-weight: 600;
|
|
||||||
outline: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.snippets {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.snippet {
|
|
||||||
position: relative;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.snippet pre {
|
|
||||||
background: #f9f9f9;
|
|
||||||
color: #333;
|
|
||||||
max-height: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.snippet button {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
import PropsView from "./PropsView.svelte";
|
import PropsView from "./PropsView.svelte";
|
||||||
import { store } from "../builderStore";
|
import { store } from "../builderStore";
|
||||||
import IconButton from "../common/IconButton.svelte";
|
import IconButton from "../common/IconButton.svelte";
|
||||||
import { LayoutIcon, PaintIcon, TerminalIcon, EventsIcon } from '../common/Icons/';
|
import { LayoutIcon, PaintIcon, TerminalIcon, CircleIndicator, EventsIcon } from '../common/Icons/';
|
||||||
import CodeEditor from './CodeEditor.svelte';
|
import CodeEditor from './CodeEditor.svelte';
|
||||||
import LayoutEditor from './LayoutEditor.svelte';
|
import LayoutEditor from './LayoutEditor.svelte';
|
||||||
import EventsEditor from "./EventsEditor";
|
import EventsEditor from "./EventsEditor";
|
||||||
|
|
||||||
let current_view = 'props';
|
let current_view = 'props';
|
||||||
|
let codeEditor;
|
||||||
|
|
||||||
$: component = $store.currentComponentInfo;
|
$: component = $store.currentComponentInfo;
|
||||||
$: originalName = component.name;
|
$: originalName = component.name;
|
||||||
|
@ -34,7 +35,12 @@
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button class:selected={current_view === 'code'} on:click={() => current_view = 'code'}>
|
<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 />
|
<TerminalIcon />
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -54,10 +60,13 @@
|
||||||
<LayoutEditor {onStyleChanged} {componentInfo}/>
|
<LayoutEditor {onStyleChanged} {componentInfo}/>
|
||||||
{:else if current_view === 'events'}
|
{:else if current_view === 'events'}
|
||||||
<EventsEditor {componentInfo} {components} {onPropChanged} />
|
<EventsEditor {componentInfo} {components} {onPropChanged} />
|
||||||
{:else}
|
|
||||||
<CodeEditor />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<CodeEditor
|
||||||
|
bind:this={codeEditor}
|
||||||
|
code={$store.currentComponentInfo._code}
|
||||||
|
onCodeChanged={store.setComponentCode} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<h1> This is a screen, this will be dealt with later</h1>
|
<h1> This is a screen, this will be dealt with later</h1>
|
||||||
|
@ -113,6 +122,7 @@ li button {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
|
@ -120,4 +130,11 @@ li button {
|
||||||
background: var(--background-button)!important;
|
background: var(--background-button)!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-indicator {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 10px;
|
||||||
|
color: var(--button-text);
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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>
|
|
@ -24,6 +24,7 @@
|
||||||
hierarchy: $store.hierarchy,
|
hierarchy: $store.hierarchy,
|
||||||
appRootPath: ""
|
appRootPath: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +40,8 @@
|
||||||
${stylesheetLinks}
|
${stylesheetLinks}
|
||||||
<script>
|
<script>
|
||||||
window["##BUDIBASE_APPDEFINITION##"] = ${JSON.stringify(appDefinition)};
|
window["##BUDIBASE_APPDEFINITION##"] = ${JSON.stringify(appDefinition)};
|
||||||
|
window["##BUDIBASE_UIFUNCTIONS"] = ${$store.currentScreenFunctions};
|
||||||
|
|
||||||
import('/_builder/budibase-client.esm.mjs')
|
import('/_builder/budibase-client.esm.mjs')
|
||||||
.then(module => {
|
.then(module => {
|
||||||
module.loadBudibase({ window, localStorage });
|
module.loadBudibase({ window, localStorage });
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
let errors = [];
|
let errors = [];
|
||||||
let props = {};
|
let props = {};
|
||||||
|
|
||||||
const props_to_ignore = ['_component','_children', '_styles', '_id'];
|
const props_to_ignore = ['_component','_children', '_styles', '_code', '_id'];
|
||||||
|
|
||||||
$: propDefs = componentInfo && Object.entries(componentInfo).filter(([name])=> !props_to_ignore.includes(name));
|
$: propDefs = componentInfo && Object.entries(componentInfo).filter(([name])=> !props_to_ignore.includes(name));
|
||||||
|
|
||||||
|
|
|
@ -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]))();
|
|
@ -6,6 +6,8 @@ export const loadBudibase = async ({
|
||||||
window, localStorage, uiFunctions }) => {
|
window, localStorage, uiFunctions }) => {
|
||||||
|
|
||||||
const appDefinition = window["##BUDIBASE_APPDEFINITION##"];
|
const appDefinition = window["##BUDIBASE_APPDEFINITION##"];
|
||||||
|
const uiFunctionsFromWindow = window["##BUDIBASE_APPDEFINITION##"];
|
||||||
|
uiFunctions = uiFunctionsFromWindow || uiFunctions;
|
||||||
|
|
||||||
const userFromStorage = localStorage.getItem("budibase:user")
|
const userFromStorage = localStorage.getItem("budibase:user")
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const _initialiseChildren = (initialiseOpts) =>
|
||||||
parentNode: treeNode,
|
parentNode: treeNode,
|
||||||
componentConstructor,uiFunctions,
|
componentConstructor,uiFunctions,
|
||||||
htmlElement, anchor, initialProps,
|
htmlElement, anchor, initialProps,
|
||||||
bb, document});
|
bb});
|
||||||
|
|
||||||
for(let comp of renderedComponentsThisIteration) {
|
for(let comp of renderedComponentsThisIteration) {
|
||||||
comp.unsubscribe = bind(comp.component);
|
comp.unsubscribe = bind(comp.component);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
export const renderComponent = ({
|
export const renderComponent = ({
|
||||||
componentConstructor, uiFunctions,
|
componentConstructor, uiFunctions,
|
||||||
htmlElement, anchor, props,
|
htmlElement, anchor, props,
|
||||||
initialProps, bb, document,
|
initialProps, bb,
|
||||||
parentNode}) => {
|
parentNode}) => {
|
||||||
|
|
||||||
const func = initialProps._id
|
const func = initialProps._id
|
||||||
|
|
|
@ -84,11 +84,9 @@ const buildIndexHtml = async (config, appname, appPath, pages, pageName) => {
|
||||||
|
|
||||||
const buildClientAppDefinition = async (config, appname, appdefinition, appPath, pages, pageName) => {
|
const buildClientAppDefinition = async (config, appname, appdefinition, appPath, pages, pageName) => {
|
||||||
|
|
||||||
|
|
||||||
const appPublicPath = publicPath(appPath, pageName);
|
const appPublicPath = publicPath(appPath, pageName);
|
||||||
const appRootPath = rootPath(config, appname);
|
const appRootPath = rootPath(config, appname);
|
||||||
|
|
||||||
|
|
||||||
const componentLibraries = [];
|
const componentLibraries = [];
|
||||||
|
|
||||||
for(let lib of pages.componentLibraries) {
|
for(let lib of pages.componentLibraries) {
|
||||||
|
@ -129,6 +127,7 @@ const buildClientAppDefinition = async (config, appname, appdefinition, appPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeFile(filename,
|
await writeFile(filename,
|
||||||
`window['##BUDIBASE_APPDEFINITION##'] = ${JSON.stringify(clientAppDefObj)}`);
|
`window['##BUDIBASE_APPDEFINITION##'] = ${JSON.stringify(clientAppDefObj)};
|
||||||
|
window['##BUDIBASE_UIFUNCTIONS##'] = ${appdefinition.uiFunctions}`);
|
||||||
|
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue