From 1413ee6f2cf823e01c109187deac574b9861f5d3 Mon Sep 17 00:00:00 2001 From: michael shanks Date: Sun, 28 Jul 2019 08:03:11 +0100 Subject: [PATCH] searching components --- packages/builder/src/builderStore/api.js | 18 ++++ packages/builder/src/builderStore/store.js | 102 +++++++++++++----- .../pagesParsing/searchComponents.js | 82 ++++++++++++++ .../tests/searchComponentsProps.spec.js | 78 ++++++++++++++ .../testApp/components/myTextBox.json | 9 +- .../components/subfolder/otherTextBox.json | 9 +- packages/server/tests/builder.spec.js | 20 ++-- packages/server/utilities/builder.js | 6 +- 8 files changed, 280 insertions(+), 44 deletions(-) create mode 100644 packages/builder/src/builderStore/api.js create mode 100644 packages/builder/src/userInterface/pagesParsing/searchComponents.js create mode 100644 packages/builder/tests/searchComponentsProps.spec.js diff --git a/packages/builder/src/builderStore/api.js b/packages/builder/src/builderStore/api.js new file mode 100644 index 0000000000..3a918ac8a5 --- /dev/null +++ b/packages/builder/src/builderStore/api.js @@ -0,0 +1,18 @@ + +const apiCall = (method) => (url, body, returnResponse=false) => + fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json', + }, + body: body && JSON.stringify(body), + }).then(r => returnResponse ? r : r.json()); + +const post = apiCall("POST"); +const get = apiCall("GET"); +const patch = apiCall("PATCH"); +const del = apiCall("DELETE"); + +export default { + post, get, patch, delete:del +}; \ No newline at end of file diff --git a/packages/builder/src/builderStore/store.js b/packages/builder/src/builderStore/store.js index bd95c2249f..30be6c9692 100644 --- a/packages/builder/src/builderStore/store.js +++ b/packages/builder/src/builderStore/store.js @@ -1,12 +1,31 @@ -import {hierarchy as hierarchyFunctions, - common, getTemplateApi } from "budibase-core"; -import {filter, cloneDeep, sortBy, map, last, keys, - cloneDeep, keyBy, - find, isEmpty, groupBy, reduce} from "lodash/fp"; -import {chain, getNode, validate, - constructHierarchy, templateApi} from "../common/core"; +import { + hierarchy as hierarchyFunctions, + common +} from "budibase-core"; +import { + filter, + cloneDeep, + sortBy, + map, + last, + keys, + cloneDeep, + concat, + find, + isEmpty, + groupBy, + reduce +} from "lodash/fp"; +import { + chain, + getNode, + validate, + constructHierarchy, + templateApi +} from "../common/core"; import {writable} from "svelte/store"; import { defaultPagesObject } from "../userInterface/pagesParsing/defaultPagesObject" +import api from "./api"; const pipe = common.$; @@ -53,6 +72,8 @@ export const getStore = () => { store.saveDerivedComponent = saveDerivedComponent(store); store.refreshComponents = refreshComponents(store); store.addComponentLibrary = addComponentLibrary(store); + store.renameDerivedComponent = renameDerivedComponent(store); + store.deleteDerivedComponent = deleteDerivedComponent(store); return store; } @@ -65,20 +86,20 @@ const initialise = (store, initial) => async () => { : ""; if(!appname) { - initial.apps = await fetch(`/_builder/api/apps`) - .then(r => r.json()); + initial.apps = await api.get(`/_builder/api/apps`); initial.hasAppPackage = false; store.set(initial); return initial; } - const pkg = await fetch(`/_builder/api/${appname}/appPackage`) - .then(r => r.json()); + const pkg = await api.get(`/_builder/api/${appname}/appPackage`); initial.appname = appname; initial.hasAppPackage = true; initial.hierarchy = pkg.appDefinition.hierarchy; initial.accessLevels = pkg.accessLevels; + initial.derivedComponents = pkg.derivedComponents; + initial.rootComponents = pkg.rootComponents; initial.actions = reduce((arr, action) => { arr.push(action); return arr; @@ -344,23 +365,53 @@ const saveDerivedComponent = store => (derivedComponent) => { s.derivedComponents = derivedComponents; - fetch(`/_builder/api/${s.appname}/derivedcomponent`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(derivedComponent), - }); + api.post(`/_builder/api/${s.appname}/derivedcomponent`, derivedComponent); return s; }) - }; +const deleteDerivedComponent = store => name => { + store.update(s => { + + const derivedComponents = pipe(s.derivedComponents, [ + filter(c => c._name !== name) + ]); + + s.derivedComponents = derivedComponents; + + api.delete(`/_builder/api/${s.appname}/derivedcomponent/${name}`); + + return s; + }) +} + +const renameDerivedComponent = store => (oldname, newname) => { + store.update(s => { + + const component = pipe(s.derivedComponents, [ + find(c => c._name === name) + ]); + + component._name = newname; + + const derivedComponents = pipe(s.derivedComponents, [ + filter(c => c._name !== name), + concat(component) + ]); + + s.derivedComponent = derivedComponents; + + api.delete(`/_builder/api/${s.appname}/derivedcomponent/${name}`); + + return s; + }) +} + const addComponentLibrary = store => async lib => { const response = - await fetch(`/_builder/api/${db.appname}/components?${encodeURI(lib)}`); + await api.get(`/_builder/api/${db.appname}/components?${encodeURI(lib)}`,undefined, true); const success = response.status === 200; @@ -390,8 +441,7 @@ const addComponentLibrary = store => async lib => { const refreshComponents = store => async () => { const components = - await fetch(`/_builder/api/${db.appname}/components`) - .then(r => jQuery.json()); + await api.get(`/_builder/api/${db.appname}/components`); const rootComponents = pipe(components, [ keys, @@ -420,11 +470,5 @@ const savePackage = (store, s) => { accessLevels:s.accessLevels } - fetch(`/_builder/api/${s.appname}/appPackage`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(data), - }); + api.post(`/_builder/api/${s.appname}/appPackage`, data); } diff --git a/packages/builder/src/userInterface/pagesParsing/searchComponents.js b/packages/builder/src/userInterface/pagesParsing/searchComponents.js new file mode 100644 index 0000000000..a55cbff9ca --- /dev/null +++ b/packages/builder/src/userInterface/pagesParsing/searchComponents.js @@ -0,0 +1,82 @@ +import { + find, + isUndefined, + filter, + some, + includes +} from "lodash/fp"; + +import { + common +} from "budibase-core"; + + +const pipe = common.$; + +const normalString = s => (s||"").trim().toLowerCase(); + +const isRootComponent = c => isUndefined(c.inherits); + +export const searchAllComponents = (derivedComponents, rootComponents, phrase) => { + + const hasPhrase = (...vals) => pipe(vals, [ + some(v => includes(phrase)(v)) + ]); + + const rootComponentMatches = c => + hasPhrase(c.name, ...(c.tags || [])); + + const derivedComponentMatches = c => { + if(hasPhrase(c.name, ...(c.tags || []))) return true; + + const parent = getExactComponent( + derivedComponents, + rootComponents, + c.inherits); + + if(isRootComponent(parent)) + return rootComponentMatches(parent); + + return derivedComponentMatches(parent); + } + + return ([ + ...filter(derivedComponentMatches)(derivedComponents), + ...filter(rootComponentMatches)(rootComponents) + ]); + +} + +export const getExactComponent = (derivedComponents, rootComponents, name) => { + + const stringEquals = (s1, s2) => + normalString(s1) === normalString(s2); + + const derived = pipe(derivedComponents,[ + find(c => stringEquals(c.name, name)) + ]); + + if(derived) return derived; + + const root = pipe(rootComponents,[ + find(c => stringEquals(c.name, name)) + ]); + + return root; +} + +export const getAncestorProps = (derivedComponents, rootComponents, name, found=[]) => { + const thisComponent = getExactComponent( + derivedComponents, rootComponents, name); + + if(isRootComponent(thisComponent)) + return [thisComponent.props, ...found]; + + return getAncestorProps( + derivedComponents, + rootComponents, + thisComponent.inherits, + [{_component:thisComponent.inherits, ...thisComponent.props}, + ...found]); + +} \ No newline at end of file diff --git a/packages/builder/tests/searchComponentsProps.spec.js b/packages/builder/tests/searchComponentsProps.spec.js new file mode 100644 index 0000000000..92b9a4072d --- /dev/null +++ b/packages/builder/tests/searchComponentsProps.spec.js @@ -0,0 +1,78 @@ +import { + searchAllComponents, + getExactComponent, + getAncestorProps +} from "../src/userInterface/pagesParsing/searchComponents"; + + +describe("searchAllComponents", () => { + + it("should match derived component by name", () => { + + const results = searchAllComponents( + derivedComponents(), + rootComponents(), + "smalltextbox" + ); + + expect(results.length).toBe(1); + expect(results[0].name).toBe("common/SmallTextbox"); + + }) + +}); + +describe("getExactComponent", () => { + +}); + +describe("getAncestorProps", () => { + +}) + +const derivedComponents = () => ([ +{ + inherits:"budibase-components/TextBox", + name: "common/SmallTextbox", + props: { + size: "small" + } +}, +{ + inherits:"common/SmallTextbox", + name: "common/PasswordBox", + tags: ["mask"], + props: { + isPassword: true + } +}, +{ + inherits:"budibase-components/Button", + name:"PrimaryButton", + props: { + css:"btn-primary" + } +} +]) + +const rootComponents = () => ([ + { + name: "budibase-components/TextBox", + tags: ["Text", "input"], + props: { + size: {type:"options", options:["small", "medium", "large"]}, + isPassword: "boolean", + placeholder: "string", + label:"string" + } + }, + { + name: "budibase-components/Button", + tags: ["input"], + props: { + size: {type:"options", options:["small", "medium", "large"]}, + css: "string", + content: "component" + } + } +]) \ No newline at end of file diff --git a/packages/server/appPackages/testApp/components/myTextBox.json b/packages/server/appPackages/testApp/components/myTextBox.json index e76040fb63..26473b22d8 100644 --- a/packages/server/appPackages/testApp/components/myTextBox.json +++ b/packages/server/appPackages/testApp/components/myTextBox.json @@ -1,4 +1,9 @@ { - "_component": "./customComponents/textbox", - "label": "hello" + "inherits": "./customComponents/textbox", + "name": "myTextBox", + "tags": [], + "description": "A text input, with a label", + "props" : { + "label": "hello" + } } \ No newline at end of file diff --git a/packages/server/appPackages/testApp/components/subfolder/otherTextBox.json b/packages/server/appPackages/testApp/components/subfolder/otherTextBox.json index 038c1f094b..d96463b586 100644 --- a/packages/server/appPackages/testApp/components/subfolder/otherTextBox.json +++ b/packages/server/appPackages/testApp/components/subfolder/otherTextBox.json @@ -1,4 +1,9 @@ { - "_component": "./moreCustomComponents/textbox", - "label": "hello" + "inherits": "./moreCustomComponents/textbox", + "name":"subfolder/otherTextBox.json", + "tags": [], + "description": "A text input, with a label", + "props" : { + "label": "hello" + } } \ No newline at end of file diff --git a/packages/server/tests/builder.spec.js b/packages/server/tests/builder.spec.js index d3bce5c635..a72f512de1 100644 --- a/packages/server/tests/builder.spec.js +++ b/packages/server/tests/builder.spec.js @@ -68,8 +68,8 @@ it("/apppackage should get derivedComponents", async () => { .expect(statusCodes.OK); const expectedComponents = { - "myTextBox" : {...derivedComponent1, _name:"myTextBox"}, - "subfolder/otherTextBox": {...derivedComponent2, _name:"subfolder/otherTextBox"} + "myTextBox" : {...derivedComponent1, name:"myTextBox"}, + "subfolder/otherTextBox": {...derivedComponent2, name:"subfolder/otherTextBox"} }; expect(body.derivedComponents).toEqual(expectedComponents); @@ -77,9 +77,11 @@ it("/apppackage should get derivedComponents", async () => { it("should be able to create new derived component", async () => { const newDerivedComponent = { - _name: "newTextBox", - _component: "./customComponents/textbox", - label: "something" + name: "newTextBox", + inherits: "./customComponents/textbox", + props: { + label: "something" + } }; await app.post("/_builder/api/testApp/derivedcomponent", newDerivedComponent) @@ -93,9 +95,11 @@ it("should be able to create new derived component", async () => { it("should be able to update derived component", async () => { const updatedDerivedComponent = { - _name: "newTextBox", - _component: "./customComponents/textbox", - label: "something else" + name: "newTextBox", + inherits: "./customComponents/textbox", + props: { + label: "something else" + } }; await app.post("/_builder/api/testApp/derivedcomponent", updatedDerivedComponent) diff --git a/packages/server/utilities/builder.js b/packages/server/utilities/builder.js index 4826e62ffd..cb5c59db46 100644 --- a/packages/server/utilities/builder.js +++ b/packages/server/utilities/builder.js @@ -41,7 +41,7 @@ module.exports.getPackageForBuilder = async (config, appname) => { rootComponents: await getRootComponents(appPath, pages), - derivedComponents: keyBy("_name")( + derivedComponents: keyBy("name")( await fetchDerivedComponents(appPath)) }) @@ -73,7 +73,7 @@ module.exports.saveDerivedComponent = async (config, appname, component) => { const appPath = appPackageFolder(config, appname); await writeJSON( - componentPath(appPath, component._name), + componentPath(appPath, component.name), component, {encoding:"utf8", flag:"w"}); } @@ -177,7 +177,7 @@ const fetchDerivedComponents = async (appPath, relativePath = "") => { const component = await readJSON(itemFullPath); - component._name = itemRelativePath + component.name = itemRelativePath .substring(0, itemRelativePath.length - 5) .replace(/\\/g, "/");