diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000..2d525873a0 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "jest": true + }, + "parserOptions": { + "ecmaVersion": 2019, + "sourceType": "module" + }, + "ignorePatterns": ["node_modules", "dist", "public"], + "plugins": ["prettier", "svelte3"], + "extends": ["eslint:recommended"], + "overrides": [ + { + "files": ["**/*.svelte"], + "processor": "svelte3/svelte3" + } + ], + "rules": { + "prettier/prettier": "error" + } +} \ No newline at end of file diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml new file mode 100644 index 0000000000..a9bad4639e --- /dev/null +++ b/.github/workflows/budibase_ci.yml @@ -0,0 +1,35 @@ +name: Budibase CI + +on: + # Trigger the workflow on push or pull request, + # but only for the master branch + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: yarn + - run: yarn lint + - run: yarn build + - run: yarn test + env: + CI: true + name: Budibase CI + diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..ce02997c90 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "tabWidth": 2, + "semi": false, + "singleQuote": false, + "trailingComma": "es5", + "plugins": ["prettier-plugin-svelte"] +} \ No newline at end of file diff --git a/package.json b/package.json index 6c39e60edf..57a3080fb7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,12 @@ "name": "root", "private": true, "devDependencies": { - "lerna": "^3.14.1" + "eslint": "^6.8.0", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-svelte3": "^2.7.3", + "lerna": "^3.14.1", + "prettier": "^1.19.1", + "prettier-plugin-svelte": "^0.7.0" }, "dependencies": {}, "scripts": { @@ -10,6 +15,9 @@ "build": "lerna run build", "initialise": "lerna run initialise", "clean": "lerna clean", - "dev": "lerna run --parallel --stream dev:builder" + "dev": "lerna run --parallel --stream dev:builder", + "test": "lerna run test", + "lint": "eslint packages", + "format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx}\"" } } diff --git a/packages/builder/package.json b/packages/builder/package.json index 5566e7e036..9595da46f6 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -6,6 +6,7 @@ "build": "rollup -c", "start": "rollup -c -w", "test": "jest", + "test:watch": "jest --watchAll", "dev:builder": "rollup -c -w" }, "jest": { diff --git a/packages/builder/rollup.config.js b/packages/builder/rollup.config.js index b5cc54873d..67507a5cfd 100644 --- a/packages/builder/rollup.config.js +++ b/packages/builder/rollup.config.js @@ -11,34 +11,34 @@ import browsersync from "rollup-plugin-browsersync"; import proxy from "http-proxy-middleware"; const target = 'http://localhost:4001'; -const _builderProxy = proxy('/_builder', { - target:"http://localhost:3000", - pathRewrite: {'^/_builder' : ''} +const _builderProxy = proxy('/_builder', { + target: "http://localhost:3000", + pathRewrite: { '^/_builder': '' } }); -const apiProxy = proxy(['/_builder/api/**', '/_builder/**/componentlibrary', '/_builder/**/componentlibraryGenerators'] , { +const apiProxy = proxy(['/_builder/api/**', '/_builder/**/componentlibrary', '/_builder/**/componentlibraryGenerators'], { target, logLevel: "debug", changeOrigin: true, cookieDomainRewrite: true, onProxyReq(proxyReq) { - if (proxyReq.getHeader("origin")) { - proxyReq.setHeader("origin", target) - } + if (proxyReq.getHeader("origin")) { + proxyReq.setHeader("origin", target) + } } - }); +}); const production = !process.env.ROLLUP_WATCH; -const lodash_fp_exports = ["union", "reduce", "isUndefined", "cloneDeep", "split", "some", "map", "filter", "isEmpty", "countBy", "includes", "last", "find", "constant", -"take", "first", "intersection", "mapValues", "isNull", "has", "isInteger", "isNumber", "isString", "isBoolean", "isDate", "isArray", "isObject", "clone", "values", "keyBy", "isNaN", -"keys", "orderBy", "concat", "reverse", "difference", "merge", "flatten", "each", "pull", "join", "defaultCase", "uniqBy", "every", "uniqWith", "isFunction", "groupBy", -"differenceBy", "intersectionBy", "isEqual", "max", "sortBy", "assign", "uniq", "trimChars", "trimCharsStart", "isObjectLike", "flattenDeep", "indexOf", "isPlainObject", -"toNumber", "takeRight"]; +const lodash_fp_exports = ["union", "reduce", "isUndefined", "cloneDeep", "split", "some", "map", "filter", "isEmpty", "countBy", "includes", "last", "find", "constant", + "take", "first", "intersection", "mapValues", "isNull", "has", "isInteger", "isNumber", "isString", "isBoolean", "isDate", "isArray", "isObject", "clone", "values", "keyBy", "isNaN", + "keys", "orderBy", "concat", "reverse", "difference", "merge", "flatten", "each", "pull", "join", "defaultCase", "uniqBy", "every", "uniqWith", "isFunction", "groupBy", + "differenceBy", "intersectionBy", "isEqual", "max", "sortBy", "assign", "uniq", "trimChars", "trimCharsStart", "isObjectLike", "flattenDeep", "indexOf", "isPlainObject", + "toNumber", "takeRight", "toPairs"]; -const lodash_exports = ["flow", "join", "replace", "trim", "dropRight", "takeRight", "head", "reduce", -"tail", "startsWith", "findIndex", "merge", -"assign", "each", "find", "orderBy", "union"]; +const lodash_exports = ["flow", "join", "replace", "trim", "dropRight", "takeRight", "head", "reduce", + "tail", "startsWith", "findIndex", "merge", + "assign", "each", "find", "orderBy", "union"]; const outputpath = "../server/builder"; @@ -63,7 +63,7 @@ export default { { src: 'src/favicon.png', dest: outputpath }, { src: 'src/assets', dest: outputpath }, { src: 'node_modules/@budibase/client/dist/budibase-client.esm.mjs', dest: outputpath }, - ] + ] }), svelte({ @@ -80,28 +80,28 @@ export default { resolve({ browser: true, dedupe: importee => { - return importee === 'svelte' - || importee.startsWith('svelte/') - || coreExternal.includes(importee); + return importee === 'svelte' + || importee.startsWith('svelte/') + || coreExternal.includes(importee); } - + }), commonjs({ namedExports: { "lodash/fp": lodash_fp_exports, - "lodash":lodash_exports, + "lodash": lodash_exports, "shortid": ["generate"] } }), url({ - limit: 0, - include: ["**/*.woff2", "**/*.png"], + limit: 0, + include: ["**/*.woff2", "**/*.png"], fileName: "[dirname][name][extname]", emitFiles: true }), url({ - limit: 0, - include: ["**/*.css"], + limit: 0, + include: ["**/*.css"], fileName: "[name][extname]", emitFiles: true }), @@ -113,7 +113,7 @@ export default { !production && livereload(outputpath), !production && browsersync({ server: outputpath, - middleware: [apiProxy,_builderProxy] + middleware: [apiProxy, _builderProxy] }), // If we're building for production (npm run build diff --git a/packages/builder/src/builderStore/generate_css.js b/packages/builder/src/builderStore/generate_css.js new file mode 100644 index 0000000000..29ddb16245 --- /dev/null +++ b/packages/builder/src/builderStore/generate_css.js @@ -0,0 +1,118 @@ +import { filter, map, reduce, toPairs } from "lodash/fp"; +import { pipe } from "../common/core"; + +const self = n => n; +const join_with = delimiter => a => a.join(delimiter); +const empty_string_to_unset = s => s.length ? s : "0"; +const add_suffix = suffix => s => s + suffix; + +export const make_margin = (values) => pipe(values, [ + map(empty_string_to_unset), + map(add_suffix('px')), + join_with(' ') +]); + +const tap = message => x => { + console.log(x); + return x; +} + +const css_map = { + templaterows: { + name: 'grid-template-columns', + generate: self + }, + templatecolumns: { + name: 'grid-template-rows', + generate: self + }, + gridarea: { + name: 'grid-area', + generate: make_margin + }, + gap: { + name: 'grid-gap', + generate: n => `${n}px` + }, + columnstart: { + name: 'grid-column-start', + generate: self + }, + columnend: { + name: 'grid-column-end', + generate: self + }, + rowstart: { + name: 'grid-row-start', + generate: self + }, + rowend: { + name: 'grid-row-end', + generate: self + }, + padding: { + name: 'padding', + generate: make_margin + }, + margin: { + name: 'margin', + generate: make_margin + }, + zindex: { + name: 'z-index', + generate: self + } +} + +export const generate_rule = ([name, values]) => + `${css_map[name].name}: ${css_map[name].generate(values)};` + +const handle_grid = (acc, [name, value]) => { + let tmp = []; + + if (name === 'row' || name === 'column') { + if (value[0]) tmp.push([`${name}start`, value[0]]); + if (value[1]) tmp.push([`${name}end`, value[1]]); + return acc.concat(tmp) + } + + return acc.concat([[name, value]]); +} + +const object_to_css_string = [ + toPairs, + reduce(handle_grid, []), + filter(v => Array.isArray(v[1]) ? v[1].some(s => s.length) : v[1].length), + map(generate_rule), + join_with('\n'), +]; + +export const generate_css = ({ layout, position }) => { + let _layout = pipe(layout, object_to_css_string); + _layout = _layout.length ? _layout + "\ndisplay: grid;" : _layout; + + return { + layout: _layout, + position: pipe(position, object_to_css_string) + } +} + +const apply_class = (id, name, styles) => `.${name}-${id} {\n${styles}\n}`; + +export const generate_screen_css = (component_array) => { + let styles = ""; + + for (let i = 0; i < component_array.length; i += 1) { + const { _styles, _id, _children } = component_array[i]; + const { layout, position } = generate_css(_styles); + + styles += apply_class(_id, 'pos', position) + "\n"; + styles += apply_class(_id, 'lay', layout) + "\n"; + + if (_children && _children.length) { + styles += generate_screen_css(_children) + "\n"; + } + + } + return styles.trim(); +} diff --git a/packages/builder/src/builderStore/store.js b/packages/builder/src/builderStore/store.js index 0975671073..c9dc3da87b 100644 --- a/packages/builder/src/builderStore/store.js +++ b/packages/builder/src/builderStore/store.js @@ -4,7 +4,7 @@ import { import { filter, cloneDeep, sortBy, map, last, keys, concat, keyBy, - find, isEmpty, reduce, values, isEqual + find, isEmpty, values, } from "lodash/fp"; import { pipe, getNode, validate, @@ -17,11 +17,13 @@ import api from "./api"; import { isRootComponent, getExactComponent } from "../userInterface/pagesParsing/searchComponents"; import { rename } from "../userInterface/pagesParsing/renameScreen"; import { - getNewComponentInfo, getScreenInfo, getComponentInfo + getNewComponentInfo, getScreenInfo, } from "../userInterface/pagesParsing/createProps"; import { loadLibs, loadLibUrls, loadGeneratorLibs } from "./loadComponentLibraries"; +import { uuid } from './uuid'; +import { generate_screen_css } from './generate_css'; let appname = ""; @@ -710,7 +712,8 @@ const addChildComponent = store => component => { const component_definition = Object.assign( cloneDeep(newComponent.fullProps), { _component: component, - _layout: {} + _styles: { position: {}, layout: {} }, + _id: uuid() }) if (children) { @@ -729,6 +732,8 @@ const addChildComponent = store => component => { _saveScreen(store, s, s.currentFrontEndItem); + _saveScreen(store, s, s.currentFrontEndItem); + return s; }) } @@ -751,12 +756,13 @@ const setComponentProp = store => (name, value) => { }) } -const setComponentStyle = store => (name, value) => { +const setComponentStyle = store => (type, name, value) => { store.update(s => { - if (!s.currentComponentInfo._layout) { - s.currentComponentInfo._layout = {}; + if (!s.currentComponentInfo._styles) { + s.currentComponentInfo._styles = {}; } - s.currentComponentInfo._layout[name] = value; + 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) diff --git a/packages/builder/src/builderStore/uuid.js b/packages/builder/src/builderStore/uuid.js new file mode 100644 index 0000000000..79ad68af94 --- /dev/null +++ b/packages/builder/src/builderStore/uuid.js @@ -0,0 +1,7 @@ +export function uuid() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0, + v = c == 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} diff --git a/packages/builder/src/common/ActionButton.svelte b/packages/builder/src/common/ActionButton.svelte new file mode 100644 index 0000000000..fca74c09ff --- /dev/null +++ b/packages/builder/src/common/ActionButton.svelte @@ -0,0 +1,53 @@ + + + + + diff --git a/packages/builder/src/common/Icons/Events.svelte b/packages/builder/src/common/Icons/Events.svelte new file mode 100644 index 0000000000..b7d92481e3 --- /dev/null +++ b/packages/builder/src/common/Icons/Events.svelte @@ -0,0 +1,9 @@ + diff --git a/packages/builder/src/common/Icons/Pencil.svelte b/packages/builder/src/common/Icons/Pencil.svelte new file mode 100644 index 0000000000..266458a132 --- /dev/null +++ b/packages/builder/src/common/Icons/Pencil.svelte @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/packages/builder/src/common/Icons/index.js b/packages/builder/src/common/Icons/index.js index 2e2ffc2010..c00ebb3ce4 100644 --- a/packages/builder/src/common/Icons/index.js +++ b/packages/builder/src/common/Icons/index.js @@ -4,3 +4,5 @@ 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'; +export { default as EventsIcon } from './Events.svelte'; +export { default as PencilIcon } from './Pencil.svelte'; diff --git a/packages/builder/src/common/Input.svelte b/packages/builder/src/common/Input.svelte new file mode 100644 index 0000000000..04938b670a --- /dev/null +++ b/packages/builder/src/common/Input.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/packages/builder/src/common/Inputs/InputGroup.svelte b/packages/builder/src/common/Inputs/InputGroup.svelte index 55bede83f2..22a272aec4 100644 --- a/packages/builder/src/common/Inputs/InputGroup.svelte +++ b/packages/builder/src/common/Inputs/InputGroup.svelte @@ -2,6 +2,7 @@ export let meta = []; export let size = ''; export let values = []; + export let type = "number"; export let onStyleChanged = () => {}; let _values = values.map(v => v); @@ -11,7 +12,7 @@