diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js index 9f91550bca..1f3ea9c0ef 100644 --- a/packages/builder/src/builderStore/store/index.js +++ b/packages/builder/src/builderStore/store/index.js @@ -5,7 +5,6 @@ import { writable, get } from "svelte/store" import api from "../api" import { DEFAULT_PAGES_OBJECT } from "../../constants" import { getExactComponent } from "components/userInterface/pagesParsing/searchComponents" -import { rename } from "components/userInterface/pagesParsing/renameScreen" import { createProps, makePropsSafe, @@ -24,6 +23,7 @@ import { saveCurrentPreviewItem as _saveCurrentPreviewItem, saveScreenApi as _saveScreenApi, regenerateCssForCurrentScreen, + renameCurrentScreen, } from "../storeUtils" export const getStore = () => { @@ -52,7 +52,6 @@ export const getStore = () => { store.createDatabaseForApp = backendStoreActions.createDatabaseForApp(store) store.saveScreen = saveScreen(store) - store.renameScreen = renameScreen(store) store.deleteScreen = deleteScreen(store) store.setCurrentScreen = setCurrentScreen(store) store.setCurrentPage = setCurrentPage(store) @@ -63,6 +62,7 @@ export const getStore = () => { store.addChildComponent = addChildComponent(store) store.selectComponent = selectComponent(store) store.setComponentProp = setComponentProp(store) + store.setPageOrScreenProp = setPageOrScreenProp(store) store.setComponentStyle = setComponentStyle(store) store.setComponentCode = setComponentCode(store) store.setScreenType = setScreenType(store) @@ -207,46 +207,6 @@ const deleteScreen = store => name => { }) } -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.currentPreviewItem.name === oldname) - s.currentPreviewItem.name = newname - - const saveAllChanged = async () => { - for (let screenName of changedScreens) { - const changedScreen = getExactComponent(screens, screenName) - await api.post(`/_builder/api/${s.appId}/screen`, changedScreen) - } - } - - api - .patch(`/_builder/api/${s.appId}/screen`, { - oldname, - newname, - }) - .then(() => saveAllChanged()) - .then(() => { - _savePage(s) - }) - - return s - }) -} - const savePage = store => async page => { store.update(state => { if (state.currentFrontEndType !== "page" || !state.currentPageName) { @@ -400,6 +360,18 @@ const setComponentProp = store => (name, value) => { }) } +const setPageOrScreenProp = store => (name, value) => { + store.update(state => { + if (name === "name" && state.currentFrontEndType === "screen") { + state = renameCurrentScreen(value, state) + } else { + state.currentPreviewItem[name] = value + _saveCurrentPreviewItem(state) + } + return state + }) +} + const setComponentStyle = store => (type, name, value) => { store.update(state => { if (!state.currentComponentInfo._styles) { diff --git a/packages/builder/src/builderStore/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js index d6aa4d0308..ff951e6b6f 100644 --- a/packages/builder/src/builderStore/storeUtils.js +++ b/packages/builder/src/builderStore/storeUtils.js @@ -45,6 +45,19 @@ export const saveScreenApi = (screen, s) => { .then(() => savePage(s)) } +export const renameCurrentScreen = (newname, state) => { + const oldname = state.currentPreviewItem.name + state.currentPreviewItem.name = newname + api.patch( + `/_builder/api/${state.appId}/pages/${state.currentPageName}/screen`, + { + oldname, + newname, + } + ) + return state +} + export const walkProps = (props, action, cancelToken = null) => { cancelToken = cancelToken || { cancelled: false } action(props, () => { diff --git a/packages/builder/src/components/userInterface/Colorpicker/ButtonGroup.svelte b/packages/builder/src/components/userInterface/Colorpicker/ButtonGroup.svelte new file mode 100644 index 0000000000..f9c402ac81 --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/ButtonGroup.svelte @@ -0,0 +1,27 @@ + + + + +
+ {#each colorFormats as text} + onclick(text)} /> + {/each} +
diff --git a/packages/builder/src/components/userInterface/Colorpicker/Colorpicker.svelte b/packages/builder/src/components/userInterface/Colorpicker/Colorpicker.svelte new file mode 100644 index 0000000000..936756f403 --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/Colorpicker.svelte @@ -0,0 +1,145 @@ + + + + +
+ + + +
+
+
+
+
+
+ setHue(hue.detail)} /> + setAlpha(alpha.detail)} /> +
+
+ +
+ + handleColorInput(event.target.value)} /> +
+
+ +
diff --git a/packages/builder/src/components/userInterface/Colorpicker/Colorpreview.svelte b/packages/builder/src/components/userInterface/Colorpicker/Colorpreview.svelte new file mode 100644 index 0000000000..fa880b6292 --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/Colorpreview.svelte @@ -0,0 +1,143 @@ + + +
+ {#if !errorMsg} +
+ {#if open} +
+ +
+
open = false} class="overlay">
+ {/if} + {:else} +
+ × +
+ {/if} +
+ + + + diff --git a/packages/builder/src/components/userInterface/Colorpicker/FlatButton.svelte b/packages/builder/src/components/userInterface/Colorpicker/FlatButton.svelte new file mode 100644 index 0000000000..af1ea5e945 --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/FlatButton.svelte @@ -0,0 +1,26 @@ + + + + +
{text}
diff --git a/packages/builder/src/components/userInterface/Colorpicker/Input.svelte b/packages/builder/src/components/userInterface/Colorpicker/Input.svelte new file mode 100644 index 0000000000..04966c1f30 --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/Input.svelte @@ -0,0 +1,28 @@ + + + + +
+ +
diff --git a/packages/builder/src/components/userInterface/Colorpicker/Palette.svelte b/packages/builder/src/components/userInterface/Colorpicker/Palette.svelte new file mode 100644 index 0000000000..01a614124c --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/Palette.svelte @@ -0,0 +1,60 @@ + + + + +
+
+
diff --git a/packages/builder/src/components/userInterface/Colorpicker/Slider.svelte b/packages/builder/src/components/userInterface/Colorpicker/Slider.svelte new file mode 100644 index 0000000000..ec8d99248d --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/Slider.svelte @@ -0,0 +1,87 @@ + + + + +
handleClick(event.clientX)} + class="color-format-slider" + class:hue={type === 'hue'} + class:alpha={type === 'alpha'}> +
handleClick(e.detail)} + class="slider-thumb" + {style} /> +
diff --git a/packages/builder/src/components/userInterface/Colorpicker/drag.js b/packages/builder/src/components/userInterface/Colorpicker/drag.js new file mode 100644 index 0000000000..0d7a9c2481 --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/drag.js @@ -0,0 +1,22 @@ +export default function(node) { + function handleMouseDown() { + window.addEventListener("mousemove", handleMouseMove) + window.addEventListener("mouseup", handleMouseUp) + } + + function handleMouseMove(event) { + let mouseX = event.clientX + node.dispatchEvent( + new CustomEvent("drag", { + detail: mouseX, + }) + ) + } + + function handleMouseUp() { + window.removeEventListener("mousedown", handleMouseDown) + window.removeEventListener("mousemove", handleMouseMove) + } + + node.addEventListener("mousedown", handleMouseDown) +} diff --git a/packages/builder/src/components/userInterface/Colorpicker/helpers.js b/packages/builder/src/components/userInterface/Colorpicker/helpers.js new file mode 100644 index 0000000000..5d9ff750af --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/helpers.js @@ -0,0 +1,14 @@ +export const buildStyle = styles => { + let str = "" + for (let s in styles) { + if (styles[s]) { + let key = convertCamel(s) + str += `${key}: ${styles[s]}; ` + } + } + return str +} + +export const convertCamel = str => { + return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) +} diff --git a/packages/builder/src/components/userInterface/Colorpicker/index.js b/packages/builder/src/components/userInterface/Colorpicker/index.js new file mode 100644 index 0000000000..21e19acc9b --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/index.js @@ -0,0 +1,2 @@ +import Colorpreview from "./Colorpreview.svelte" +export default Colorpreview diff --git a/packages/builder/src/components/userInterface/Colorpicker/utils.js b/packages/builder/src/components/userInterface/Colorpicker/utils.js new file mode 100644 index 0000000000..e14c8711ab --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/utils.js @@ -0,0 +1,279 @@ +export const isValidHex = str => + /^#(?:[A-F0-9]{3}$|[A-F0-9]{4}$|[A-F0-9]{6}$|[A-F0-9]{8})$/gi.test(str) + +const getHexaValues = hexString => { + if (hexString.length <= 5) { + let hexArr = hexString.match(/[A-F0-9]/gi) + let t = hexArr.map(c => (c += c)) + return t + } else { + return hexString.match(/[A-F0-9]{2}/gi) + } +} + +export const isValidRgb = str => { + const hasValidStructure = /^(?:rgba\(|rgb\()(?:[0-9,\s]|\.(?=\d))*\)$/gi.test( + str + ) + if (hasValidStructure) { + return testRgbaValues(str.toLowerCase()) + } +} + +const findNonNumericChars = /[a-z()\s]/gi + +export const getNumericValues = str => + str + .replace(findNonNumericChars, "") + .split(",") + .map(v => (v !== "" ? v : undefined)) + +export const testRgbaValues = str => { + const rgba = getNumericValues(str) + const [r, g, b, a] = rgba + + let isValidLengthRange = + (str.startsWith("rgb(") && rgba.length === 3) || + (str.startsWith("rgba(") && rgba.length === 4) + let isValidColorRange = [r, g, b].every(v => v >= 0 && v <= 255) + let isValidAlphaRange = str.startsWith("rgba(") + ? `${a}`.length <= 4 && a >= 0 && a <= 1 + : true + + return isValidLengthRange && isValidColorRange && isValidAlphaRange +} + +export const isValidHsl = str => { + const hasValidStructure = /^(?:hsl\(|hsla\()(?:[0-9,%\s]|\.(?=\d))*\)$/gi.test( + str + ) + if (hasValidStructure) { + return testHslaValues(str.toLowerCase()) + } +} + +export const testHslaValues = str => { + const hsla = getNumericValues(str) + const [h, s, l, a] = hsla + const isUndefined = [h, s, l].some(v => v === undefined) + + if (isUndefined) return false + + let isValidLengthRange = + (str.startsWith("hsl(") && hsla.length === 3) || + (str.startsWith("hsla(") && hsla.length === 4) + let isValidHue = h >= 0 && h <= 360 + let isValidSatLum = [s, l].every( + v => v.endsWith("%") && parseInt(v) >= 0 && parseInt(v) <= 100 + ) + let isValidAlphaRange = str.startsWith("hsla(") + ? `${a}`.length <= 4 && a >= 0 && a <= 1 + : true + + return isValidLengthRange && isValidHue && isValidSatLum && isValidAlphaRange +} + +export const getColorFormat = color => { + if (typeof color === "string") { + if (isValidHex(color)) { + return "hex" + } else if (isValidRgb(color)) { + return "rgb" + } else if (isValidHsl(color)) { + return "hsl" + } + } +} + +export const convertToHSVA = (value, format) => { + switch (format) { + case "hex": + return getAndConvertHexa(value) + case "rgb": + return getAndConvertRgba(value) + case "hsl": + return getAndConvertHsla(value) + } +} + +export const convertHsvaToFormat = (hsva, format) => { + switch (format) { + case "hex": + return hsvaToHexa(hsva, true) + case "rgb": + return hsvaToRgba(hsva, true) + case "hsl": + return hsvaToHsla(hsva) + } +} + +export const getAndConvertHexa = color => { + let [rHex, gHex, bHex, aHex] = getHexaValues(color) + return hexaToHSVA([rHex, gHex, bHex], aHex) +} + +export const getAndConvertRgba = color => { + let rgba = getNumericValues(color) + return rgbaToHSVA(rgba) +} + +export const getAndConvertHsla = color => { + let hsla = getNumericValues(color) + return hslaToHSVA(hsla) +} + +export const hexaToHSVA = (hex, alpha = "FF") => { + const rgba = hex + .map(v => parseInt(v, 16)) + .concat(Number((parseInt(alpha, 16) / 255).toFixed(2))) + return rgbaToHSVA(rgba) +} + +export const rgbaToHSVA = rgba => { + const [r, g, b, a = 1] = rgba + let hsv = _rgbToHSV([r, g, b]) + return [...hsv, a].map(x => parseFloat(x)) +} + +export const hslaToHSVA = ([h, s, l, a = 1]) => { + let sat = s.replace(/%/, "") + let lum = l.replace(/%/, "") + let hsv = _hslToHSV([h, sat, lum]) + return [...hsv, a].map(x => parseFloat(x)) +} + +export const hsvaToHexa = (hsva, asString = false) => { + const [r, g, b, a] = hsvaToRgba(hsva) + + const hexa = [r, g, b] + .map(v => { + let hex = Math.round(v).toString(16) + return hex.length === 1 ? `0${hex}` : hex + }) + .concat(Math.round(a * 255).toString(16)) + return asString ? `#${hexa.join("")}` : hexa +} + +export const hsvaToRgba = ([h, s, v, a = 1], asString = false) => { + let rgb = _hsvToRgb([h, s, v]).map(x => Math.round(x)) + let rgba = [...rgb, a < 1 ? _fixNum(a, 2) : a] + return asString ? `rgba(${rgba.join(",")})` : rgba +} + +export const hsvaToHsla = ([h, s, v, a = 1]) => { + let [hue, sat, lum] = _hsvToHSL([h, s, v]) + let hsla = [hue, sat + "%", lum + "%", a < 1 ? _fixNum(a, 2) : a] + return `hsla(${hsla.join(",")})` +} + +export const _hslToHSV = hsl => { + const h = hsl[0] + let s = hsl[1] / 100 + let l = hsl[2] / 100 + let smin = s + const lmin = Math.max(l, 0.01) + + l *= 2 + s *= l <= 1 ? l : 2 - l + smin *= lmin <= 1 ? lmin : 2 - lmin + const v = (l + s) / 2 + const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s) + + return [h, sv * 100, v * 100] +} + +//Credit : https://github.com/Qix-/color-convert +export const _rgbToHSV = rgb => { + let rdif + let gdif + let bdif + let h + let s + + const r = rgb[0] / 255 + const g = rgb[1] / 255 + const b = rgb[2] / 255 + const v = Math.max(r, g, b) + const diff = v - Math.min(r, g, b) + const diffc = function(c) { + return (v - c) / 6 / diff + 1 / 2 + } + + if (diff === 0) { + h = 0 + s = 0 + } else { + s = diff / v + rdif = diffc(r) + gdif = diffc(g) + bdif = diffc(b) + + if (r === v) { + h = bdif - gdif + } else if (g === v) { + h = 1 / 3 + rdif - bdif + } else if (b === v) { + h = 2 / 3 + gdif - rdif + } + + if (h < 0) { + h += 1 + } else if (h > 1) { + h -= 1 + } + } + + const hsvResult = [h * 360, s * 100, v * 100].map(v => Math.round(v)) + return hsvResult +} + +//Credit : https://github.com/Qix-/color-convert +export const _hsvToRgb = hsv => { + const h = hsv[0] / 60 + const s = hsv[1] / 100 + let v = hsv[2] / 100 + const hi = Math.floor(h) % 6 + + const f = h - Math.floor(h) + const p = 255 * v * (1 - s) + const q = 255 * v * (1 - s * f) + const t = 255 * v * (1 - s * (1 - f)) + v *= 255 + + switch (hi) { + case 0: + return [v, t, p] + case 1: + return [q, v, p] + case 2: + return [p, v, t] + case 3: + return [p, q, v] + case 4: + return [t, p, v] + case 5: + return [v, p, q] + } +} + +//Credit : https://github.com/Qix-/color-convert +export const _hsvToHSL = hsv => { + const h = hsv[0] + const s = hsv[1] / 100 + const v = hsv[2] / 100 + const vmin = Math.max(v, 0.01) + let sl + let l + + l = (2 - s) * v + const lmin = (2 - s) * vmin + sl = s * vmin + sl /= lmin <= 1 ? lmin : 2 - lmin + sl = sl || 0 + l /= 2 + + return [_fixNum(h, 0), _fixNum(sl * 100, 0), _fixNum(l * 100, 0)] +} + +export const _fixNum = (value, decimalPlaces) => + Number(parseFloat(value).toFixed(decimalPlaces)) diff --git a/packages/builder/src/components/userInterface/Colorpicker/utils.test.js b/packages/builder/src/components/userInterface/Colorpicker/utils.test.js new file mode 100644 index 0000000000..c47d54ed1b --- /dev/null +++ b/packages/builder/src/components/userInterface/Colorpicker/utils.test.js @@ -0,0 +1,106 @@ +import { getColorFormat, convertToHSVA, convertHsvaToFormat } from "./utils" + +describe("convertToHSVA - convert to hsva from format", () => { + test("convert from hexa", () => { + expect(convertToHSVA("#f222d382", "hex")).toEqual([309, 86, 95, 0.51]) + }) + + test("convert from hex", () => { + expect(convertToHSVA("#f222d3", "hex")).toEqual([309, 86, 95, 1]) + }) + + test("convert from rgba", () => { + expect(convertToHSVA("rgba(242, 34, 211, 1)", "rgb")).toEqual([ + 309, + 86, + 95, + 1, + ]) + }) + + test("convert from rgb", () => { + expect(convertToHSVA("rgb(150, 80, 255)", "rgb")).toEqual([264, 69, 100, 1]) + }) + + test("convert from from hsl", () => { + expect(convertToHSVA("hsl(264, 100%, 65.7%)", "hsl")).toEqual([ + 264, + 68.6, + 100, + 1, + ]) + }) + + test("convert from from hsla", () => { + expect(convertToHSVA("hsla(264, 100%, 65.7%, 0.51)", "hsl")).toEqual([ + 264, + 68.6, + 100, + 0.51, + ]) + }) +}) + +describe("convertHsvaToFormat - convert from hsva to format", () => { + test("Convert to hexa", () => { + expect(convertHsvaToFormat([264, 68.63, 100, 0.5], "hex")).toBe("#9650ff80") + }) + + test("Convert to rgba", () => { + expect(convertHsvaToFormat([264, 68.63, 100, 0.75], "rgb")).toBe( + "rgba(150,80,255,0.75)" + ) + }) + + test("Convert to hsla", () => { + expect(convertHsvaToFormat([264, 68.63, 100, 1], "hsl")).toBe( + "hsla(264,100%,66%,1)" + ) + }) +}) + +describe("Get Color Format", () => { + test("Testing valid hex string", () => { + expect(getColorFormat("#FFF")).toBe("hex") + }) + + test("Testing invalid hex string", () => { + expect(getColorFormat("#FFZ")).toBeUndefined() + }) + + test("Testing valid hex with alpha", () => { + expect(getColorFormat("#FF00BB80")).toBe("hex") + }) + + test("Test valid rgb value", () => { + expect(getColorFormat("RGB(255, 20, 50)")).toBe("rgb") + }) + + test("Testing invalid rgb value", () => { + expect(getColorFormat("rgb(255, 0)")).toBeUndefined() + }) + + test("Testing rgb value with alpha", () => { + expect(getColorFormat("rgba(255, 0, 50, 0.5)")).toBe("rgb") + }) + + test("Testing rgb value with incorrectly provided alpha", () => { + expect(getColorFormat("rgb(255, 0, 50, 0.5)")).toBeUndefined() + }) + + test("Testing invalid hsl value", () => { + expect(getColorFormat("hsla(255, 0)")).toBeUndefined() + }) + + test("Testing hsla value with alpha", () => { + expect(getColorFormat("hsla(150, 60%, 50%, 0.5)")).toBe("hsl") + }) + + test("Testing hsl value with incorrectly provided alpha", () => { + expect(getColorFormat("hsl(150, 0, 50, 0.5)")).toBeUndefined() + }) + + test("Testing out of bounds hsl", () => { + expect(getColorFormat("hsl(375, 0, 50)")).toBeUndefined() + }) +}) diff --git a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte index 7d33b9c963..88ce4edca9 100644 --- a/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte +++ b/packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte @@ -13,7 +13,6 @@ import CodeEditor from "./CodeEditor.svelte" import LayoutEditor from "./LayoutEditor.svelte" import EventsEditor from "./EventsEditor" - import panelStructure from "./temporaryPanelStructure.js" import CategoryTab from "./CategoryTab.svelte" import DesignView from "./DesignView.svelte" @@ -40,28 +39,9 @@ let panelDefinition = {} - $: { - if (componentPropDefinition.properties) { - if (selectedCategory.value === "design") { - panelDefinition = componentPropDefinition.properties["design"] - } else { - let panelDef = componentPropDefinition.properties["settings"] - if ( - $store.currentFrontEndType === "page" && - $store.currentView !== "component" - ) { - panelDefinition = [...page, ...panelDef] - } else if ( - $store.currentFrontEndType === "screen" && - $store.currentView !== "component" - ) { - panelDefinition = [...screen, ...panelDef] - } else { - panelDefinition = panelDef - } - } - } - } + $: panelDefinition = + componentPropDefinition.properties && + componentPropDefinition.properties[selectedCategory.value] const onStyleChanged = store.setComponentStyle const onPropChanged = store.setComponentProp @@ -107,7 +87,9 @@ {componentInstance} {componentDefinition} {panelDefinition} - onChange={onPropChanged} /> + onChange={onPropChanged} + onScreenPropChange={store.setPageOrScreenProp} + screenOrPageInstance={$store.currentView !== 'component' && $store.currentPreviewItem} /> {:else if selectedCategory.value === 'events'} {/if} @@ -121,8 +103,6 @@ height: 100%; display: flex; flex-direction: column; - overflow-x: hidden; - overflow-y: hidden; padding: 20px; box-sizing: border-box; } @@ -140,6 +120,5 @@ margin-top: 20px; flex: 1 1 auto; min-height: 0; - overflow-y: auto; } diff --git a/packages/builder/src/components/userInterface/DesignView.svelte b/packages/builder/src/components/userInterface/DesignView.svelte index 070ba502e3..6826bddc5c 100644 --- a/packages/builder/src/components/userInterface/DesignView.svelte +++ b/packages/builder/src/components/userInterface/DesignView.svelte @@ -31,6 +31,7 @@
+
{#if propertyGroupNames.length > 0} {#each propertyGroupNames as groupName} @@ -49,6 +50,7 @@ {/if}
+