├── env.d.ts ├── public └── favicon.ico ├── .vscode └── extensions.json ├── src ├── builder │ ├── modules │ │ ├── form-builder │ │ │ ├── navbar │ │ │ │ ├── navbar-input │ │ │ │ │ ├── NavbarInput.scss │ │ │ │ │ ├── NavbarInput.vue │ │ │ │ │ └── NavbarInput.ts │ │ │ │ ├── navbar-style │ │ │ │ │ ├── NavbarStyle.ts │ │ │ │ │ ├── NavbarStyle.scss │ │ │ │ │ └── NavbarStyle.vue │ │ │ │ ├── navbar-validation │ │ │ │ │ ├── NavbarValidation.ts │ │ │ │ │ ├── NavbarValidation.scss │ │ │ │ │ └── NavbarValidation.vue │ │ │ │ ├── navbar-property │ │ │ │ │ ├── NavbarProperty.scss │ │ │ │ │ ├── NavbarProperty.ts │ │ │ │ │ └── NavbarProperty.vue │ │ │ │ ├── navbar-header │ │ │ │ │ ├── NavbarHeader.ts │ │ │ │ │ ├── NavbarHeader.vue │ │ │ │ │ └── NavbarHeader.scss │ │ │ │ └── navbar-layout │ │ │ │ │ ├── NavbarLayout.scss │ │ │ │ │ ├── NavbarLayout.vue │ │ │ │ │ └── NavbarLayout.ts │ │ │ ├── form-area │ │ │ │ ├── form-layout │ │ │ │ │ ├── FormLayout.ts │ │ │ │ │ ├── FormLayout.vue │ │ │ │ │ └── FormLayout.scss │ │ │ │ └── form-item │ │ │ │ │ ├── FormItem.ts │ │ │ │ │ ├── FormItem.vue │ │ │ │ │ └── FormItem.scss │ │ │ └── header │ │ │ │ ├── Header.vue │ │ │ │ ├── Header.ts │ │ │ │ └── Header.scss │ │ ├── global │ │ │ ├── button │ │ │ │ ├── Button.ts │ │ │ │ ├── Button.vue │ │ │ │ └── Button.scss │ │ │ └── switch │ │ │ │ ├── Switch.ts │ │ │ │ ├── Switch.vue │ │ │ │ └── Switch.scss │ │ └── parser │ │ │ ├── Parser.scss │ │ │ ├── Parser.vue │ │ │ └── Parser.ts │ ├── pages │ │ ├── component-test │ │ │ ├── ComponentTest.scss │ │ │ ├── ComponentTest.vue │ │ │ └── ComponentTest.ts │ │ ├── form-builder │ │ │ ├── FormBuilder.vue │ │ │ ├── FormBuilder.ts │ │ │ └── FormBuilder.scss │ │ └── main │ │ │ ├── Main.ts │ │ │ ├── Main.vue │ │ │ └── Main.scss │ ├── builder │ │ ├── Builder.scss │ │ ├── Builder.vue │ │ └── Builder.ts │ ├── utils │ │ ├── EventBus.ts │ │ └── FindParentNode.ts │ ├── index.ts │ ├── hooks │ │ ├── modules │ │ │ └── StyleController.ts │ │ ├── UseMessages.ts │ │ └── UseDrag.ts │ ├── components │ │ ├── text-area │ │ │ ├── TextArea.scss │ │ │ ├── TextArea.vue │ │ │ └── TextArea.ts │ │ ├── select │ │ │ ├── Select.scss │ │ │ ├── Select.vue │ │ │ └── Select.ts │ │ ├── input │ │ │ ├── Input.scss │ │ │ ├── Input.vue │ │ │ └── Input.ts │ │ ├── check-box │ │ │ ├── CheckBox.scss │ │ │ ├── CheckBox.vue │ │ │ └── CheckBox.ts │ │ ├── ComponentInterfaces.ts │ │ └── option-list │ │ │ ├── OptionList.ts │ │ │ ├── OptionList.vue │ │ │ └── OptionList.scss │ ├── interfaces │ │ ├── AppState.ts │ │ ├── DragInterface.ts │ │ └── Messages.ts │ ├── assets │ │ ├── form-layout │ │ │ ├── trash.svg │ │ │ └── drag.svg │ │ └── header │ │ │ ├── form.svg │ │ │ └── arrow-back.svg │ └── constants │ │ └── DefaultMessages.ts ├── Global.scss ├── main.ts └── App.vue ├── tsconfig.vite-config.json ├── vite.config.ts ├── index.html ├── tsconfig.json ├── .gitignore ├── package.json ├── Readme.md └── LICENSE.MD /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Denizhan1103/vivi-form-builder/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-input/NavbarInput.scss: -------------------------------------------------------------------------------- 1 | .navbarinput { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 8px; 5 | } -------------------------------------------------------------------------------- /src/builder/pages/component-test/ComponentTest.scss: -------------------------------------------------------------------------------- 1 | .test { 2 | display: grid; 3 | grid-template-columns: repeat(2,1fr); 4 | padding: 20px; 5 | grid-gap: 20px; 6 | } -------------------------------------------------------------------------------- /tsconfig.vite-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-style/NavbarStyle.ts: -------------------------------------------------------------------------------- 1 | import { useMessages } from "@/builder/hooks/UseMessages" 2 | 3 | export default { 4 | setup() { 5 | const messages = useMessages('builderPage.layout') 6 | 7 | return { 8 | messages 9 | } 10 | }, 11 | } -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-validation/NavbarValidation.ts: -------------------------------------------------------------------------------- 1 | import { useMessages } from "@/builder/hooks/UseMessages" 2 | 3 | export default { 4 | setup() { 5 | const messages = useMessages('builderPage.layout') 6 | 7 | return { 8 | messages 9 | } 10 | }, 11 | } -------------------------------------------------------------------------------- /src/builder/builder/Builder.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | font-family: 'Roboto', sans-serif; 8 | font-size: 14px; 9 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue()], 9 | resolve: { 10 | alias: { 11 | '@': fileURLToPath(new URL('./src', import.meta.url)) 12 | } 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/builder/modules/global/button/Button.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | width: { 4 | type: String, 5 | required: false, 6 | default: 'Full' 7 | }, 8 | disabled: { 9 | type: Boolean, 10 | required: false, 11 | default: false 12 | } 13 | }, 14 | setup() { 15 | 16 | }, 17 | } -------------------------------------------------------------------------------- /src/builder/modules/parser/Parser.scss: -------------------------------------------------------------------------------- 1 | .parser { 2 | width: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | gap: 8px; 6 | 7 | &__listitem { 8 | display: flex; 9 | align-items: center; 10 | gap: 4px; 11 | 12 | &-header { 13 | font-weight: 600; 14 | } 15 | 16 | &-value { 17 | font-weight: 400; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-style/NavbarStyle.scss: -------------------------------------------------------------------------------- 1 | .navbarstyle { 2 | height: 60px; 3 | display: grid; 4 | place-items: center; 5 | border-radius: 6px; 6 | border: 6px solid #1D7775; 7 | cursor: pointer; 8 | width: 100%; 9 | 10 | &__text { 11 | font-weight: 600; 12 | color: #1D7775; 13 | font-size: 14px; 14 | text-align: center; 15 | } 16 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "isolatedModules": false, 6 | "preserveValueImports": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./src/*"] 10 | } 11 | }, 12 | 13 | "references": [ 14 | { 15 | "path": "./tsconfig.vite-config.json" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-validation/NavbarValidation.scss: -------------------------------------------------------------------------------- 1 | .navbarvalidation { 2 | height: 60px; 3 | display: grid; 4 | place-items: center; 5 | border-radius: 6px; 6 | border: 6px solid #1D7775; 7 | cursor: pointer; 8 | width: 100%; 9 | 10 | &__text { 11 | font-weight: 600; 12 | color: #1D7775; 13 | font-size: 14px; 14 | text-align: center; 15 | } 16 | } -------------------------------------------------------------------------------- /src/builder/utils/EventBus.ts: -------------------------------------------------------------------------------- 1 | const eventBus = { 2 | 3 | on: (key: string, cb: any) => { 4 | window.addEventListener(key, (e: any) => cb(e.detail)) 5 | }, 6 | 7 | remove: (key: string, cb: any) => { 8 | window.removeEventListener(key, cb) 9 | }, 10 | 11 | dispatch: (key: string, data: any) => { 12 | window.dispatchEvent(new CustomEvent(key, { detail: data })) 13 | } 14 | 15 | } 16 | 17 | export default eventBus -------------------------------------------------------------------------------- /src/builder/index.ts: -------------------------------------------------------------------------------- 1 | import Builder from "./builder/Builder.vue" 2 | import Parser from "./modules/parser/Parser.vue" 3 | 4 | import { setLocale } from "./hooks/UseMessages" 5 | 6 | export { 7 | setLocale, 8 | Builder, 9 | Parser 10 | } 11 | 12 | export default { 13 | // Todo fix typing 14 | install(Vue: any, options: any) { 15 | Vue.component('ViviBuilder', Builder) 16 | Vue.component('ViviParser', Parser) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-style/NavbarStyle.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-validation/NavbarValidation.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /src/builder/builder/Builder.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /src/builder/pages/form-builder/FormBuilder.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /src/builder/hooks/modules/StyleController.ts: -------------------------------------------------------------------------------- 1 | interface SetStyleProperty { 2 | styles: any; 3 | key: string; 4 | value: string; 5 | } 6 | 7 | interface RemoveStyleProperty { 8 | styles: any; 9 | key: string; 10 | } 11 | 12 | export const setStyleProperty = ({ styles, key, value }: SetStyleProperty): any => { 13 | return styles[key] = value 14 | } 15 | 16 | export const removeStyleProperty = ({ styles, key }: RemoveStyleProperty): any => { 17 | return delete styles[key] 18 | } 19 | 20 | export const clearAllStyles = (): any => { 21 | return {} 22 | } -------------------------------------------------------------------------------- /src/builder/modules/global/switch/Switch.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from "vue"; 2 | 3 | interface Accessor { 4 | name: string; 5 | accessor: string; 6 | } 7 | 8 | export default { 9 | props: { 10 | title: { 11 | type: String, 12 | required: true 13 | }, 14 | keys: { 15 | type: Array as PropType, 16 | required: true 17 | }, 18 | activeKey: { 19 | type: String, 20 | required: true 21 | } 22 | }, 23 | setup() { 24 | 25 | }, 26 | } -------------------------------------------------------------------------------- /src/builder/pages/component-test/ComponentTest.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-header/NavbarHeader.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /src/builder/components/select/Select.scss: -------------------------------------------------------------------------------- 1 | .select { 2 | width: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | 6 | &__title { 7 | font-size: 14px; 8 | color: #5E5873; 9 | font-weight: 600; 10 | margin-bottom: 2px; 11 | } 12 | 13 | &__inner { 14 | display: flex; 15 | flex-direction: column; 16 | } 17 | 18 | &__input { 19 | outline: none; 20 | height: 28px; 21 | border-radius: 6px; 22 | border: 1px solid #5E5873; 23 | margin-bottom: 2px; 24 | padding: 0 6px; 25 | font-size: 14px; 26 | color: #5E5873; 27 | cursor: pointer; 28 | 29 | &-placeholder { 30 | display: none; 31 | } 32 | } 33 | 34 | &__validation { 35 | color: #DC143C; 36 | font-weight: 600; 37 | } 38 | } -------------------------------------------------------------------------------- /src/builder/modules/global/button/Button.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | width: 122px; 3 | height: 36px; 4 | box-shadow: 0px 5px 30px rgba(0, 0, 0, 0.15); 5 | border-radius: 6px; 6 | background-color: #1D7775; 7 | color: #FFFFFF; 8 | font-weight: 600; 9 | display: grid; 10 | place-items: center; 11 | cursor: pointer; 12 | transition: 0.3s ease background-color; 13 | outline: none; 14 | border: transparent; 15 | 16 | &:disabled { 17 | pointer-events: none; 18 | filter: opacity(0.8) grayscale(0.2); 19 | } 20 | 21 | &:hover { 22 | background-color: #186462; 23 | } 24 | 25 | &:active { 26 | background-color: #13514f; 27 | transition: 0s ease background-color; 28 | } 29 | 30 | &__full { 31 | width: 100%; 32 | } 33 | 34 | &__half { 35 | width: 50%; 36 | } 37 | } -------------------------------------------------------------------------------- /src/builder/components/input/Input.scss: -------------------------------------------------------------------------------- 1 | .input { 2 | display: flex; 3 | flex-direction: column; 4 | flex: 1; 5 | grid-column: auto / span 2; 6 | 7 | &__header { 8 | font-size: 14px; 9 | color: #5E5873; 10 | font-weight: 600; 11 | margin-bottom: 2px; 12 | } 13 | 14 | &__input { 15 | outline: none; 16 | height: 24px; 17 | border-radius: 6px; 18 | border: 1px solid #5E5873; 19 | margin-bottom: 2px; 20 | padding: 0 6px; 21 | font-size: 14px; 22 | color: #5E5873; 23 | 24 | &::placeholder { 25 | color: #5E5873; 26 | font-size: 14px; 27 | } 28 | } 29 | 30 | &__validation { 31 | font-weight: 600; 32 | font-size: 14px; 33 | color: red; 34 | } 35 | 36 | &__half { 37 | grid-column: auto / span 1 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/builder/modules/global/switch/Switch.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 26 | 27 | -------------------------------------------------------------------------------- /src/builder/utils/FindParentNode.ts: -------------------------------------------------------------------------------- 1 | interface FindParentNode { 2 | targetNode: HTMLElement; 3 | parentClassName: string; 4 | } 5 | 6 | export const findParentNode = ({ targetNode, parentClassName }: FindParentNode): any => { 7 | // Check element has parentClassName class 8 | let isParentNode: boolean | undefined = undefined 9 | targetNode.classList.forEach(className => { if (className == parentClassName) isParentNode = true }) 10 | if (isParentNode) return targetNode 11 | // Check parent node 12 | let itemNode: HTMLElement | undefined = undefined 13 | const parentNode = targetNode.parentElement 14 | if (parentNode) parentNode.classList.forEach(className => { if (className == parentClassName) itemNode = parentNode }) 15 | if (itemNode) return itemNode 16 | // Check element is HTML 17 | if (parentNode?.tagName == 'HTML') return false 18 | // Recall function 19 | if (!itemNode && parentNode) return findParentNode({ targetNode: parentNode, parentClassName }) 20 | } -------------------------------------------------------------------------------- /src/builder/components/input/Input.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-layout/NavbarLayout.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 23 | 24 | -------------------------------------------------------------------------------- /src/builder/pages/form-builder/FormBuilder.ts: -------------------------------------------------------------------------------- 1 | import { inject } from "vue" 2 | import { CurrentPage, type AppState } from "../../interfaces/AppState"; 3 | 4 | import Input from "../../components/input/Input.vue" 5 | import Header from "../../modules/form-builder/header/Header.vue"; 6 | import FormLayout from "../../modules/form-builder/form-area/form-layout/FormLayout.vue" 7 | import NavbarLayout from "@/builder/modules/form-builder/navbar/navbar-layout/NavbarLayout.vue"; 8 | 9 | enum NavbarStatus { 10 | input = 'Input', 11 | prototype = 'Prototype', 12 | advanced = 'Advanced' 13 | } 14 | 15 | export default { 16 | components: { 17 | Input, 18 | Header, 19 | FormLayout, 20 | NavbarLayout 21 | }, 22 | setup() { 23 | const appState = inject('appState') as AppState 24 | 25 | const routeToMain = () => { 26 | appState.currentPage = CurrentPage.main 27 | } 28 | 29 | return { 30 | NavbarStatus, 31 | routeToMain, 32 | } 33 | }, 34 | } -------------------------------------------------------------------------------- /src/builder/modules/form-builder/form-area/form-layout/FormLayout.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /src/builder/interfaces/AppState.ts: -------------------------------------------------------------------------------- 1 | export enum CurrentPage { 2 | main = 'Main', 3 | builder = 'FormBuilder', 4 | componentTest = 'ComponentTest' 5 | } 6 | 7 | export interface AppState { 8 | currentPage: CurrentPage; 9 | options: Options; 10 | } 11 | 12 | export interface Options { 13 | newItemCreatable: boolean; 14 | formList: Form[]; 15 | } 16 | 17 | interface Form { 18 | id: number; 19 | name: string; 20 | description?: string; 21 | nameChangable?: boolean; 22 | canStyleChangable?: boolean; 23 | canValidationChangable?: boolean; 24 | itemList: Item[]; 25 | } 26 | 27 | interface Item { 28 | id: number; 29 | type: ItemTypes; 30 | properties?: ItemProperties; 31 | } 32 | 33 | enum ItemTypes { 34 | text = 'Text', 35 | number = 'Number', 36 | date = 'Date', 37 | time = 'Time', 38 | textArea = 'TextArea', 39 | select = 'Select', 40 | checkBox = 'CheckBox' 41 | } 42 | 43 | interface ItemProperties { 44 | startingText?: string; 45 | placeholder?: string; 46 | header?: string; 47 | size?: ItemSize; 48 | values?: { id: number, value: string }[]; 49 | activeValue?: { id: number; value: string }; 50 | } 51 | 52 | enum ItemSize { 53 | half = 'Half', 54 | full = 'Full' 55 | } 56 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/header/Header.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 27 | 28 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/header/Header.ts: -------------------------------------------------------------------------------- 1 | import { inject, ref, watch } from "vue" 2 | 3 | import { useDrag } from "@/builder/hooks/UseDrag" 4 | import { useMessages } from "@/builder/hooks/UseMessages" 5 | 6 | import Button from "../../global/button/Button.vue" 7 | 8 | export default { 9 | components: { 10 | Button 11 | }, 12 | setup(props: any, { emit }: any) { 13 | const messages = useMessages('builderPage.header') 14 | 15 | const { state, updateCurrentFormName, applyCurrentForm, checkCurrentForm } = useDrag() 16 | 17 | const formName = ref(state.currentForm?.name || '') 18 | const isButtonEnable = ref(checkCurrentForm()) 19 | 20 | watch(state, () => isButtonEnable.value = checkCurrentForm()) 21 | 22 | watch(formName, (newValue: string) => { 23 | if (state.currentForm?.nameChangable == true || !state.currentForm) { 24 | updateCurrentFormName(newValue) 25 | } 26 | }) 27 | 28 | const onGoBack = () => { 29 | emit('onGoBack') 30 | } 31 | 32 | return { 33 | formName, 34 | state, 35 | messages, 36 | isButtonEnable, 37 | applyCurrentForm, 38 | onGoBack 39 | } 40 | }, 41 | } -------------------------------------------------------------------------------- /src/builder/components/check-box/CheckBox.scss: -------------------------------------------------------------------------------- 1 | .radio { 2 | 3 | &__title { 4 | font-size: 14px; 5 | color: #5E5873; 6 | font-weight: 600; 7 | margin-bottom: 2px; 8 | } 9 | 10 | &__inner { 11 | display: flex; 12 | flex-direction: column; 13 | gap: 4px; 14 | padding-left: 4px; 15 | } 16 | 17 | &__item { 18 | display: flex; 19 | align-items: center; 20 | gap: 4px; 21 | cursor: pointer; 22 | 23 | &-icon { 24 | width: 12px; 25 | height: 12px; 26 | border-radius: 50%; 27 | border: 2px solid #1D7775; 28 | transition: 0.2s ease background-color, box-shadow; 29 | 30 | &-selected { 31 | background-color: #1D7775; 32 | box-shadow: 0px 0px 5px #1d7775; 33 | } 34 | } 35 | 36 | &-text { 37 | color: #5E5873; 38 | font-size: 14px; 39 | transition: 0.2s ease font-weight; 40 | 41 | &-selected { 42 | font-weight: 600; 43 | } 44 | } 45 | } 46 | 47 | &__validation { 48 | color: #DC143C; 49 | font-size: 14px; 50 | } 51 | 52 | &__nooption { 53 | color: #5E5873; 54 | font-size: 12px; 55 | } 56 | } -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-header/NavbarHeader.scss: -------------------------------------------------------------------------------- 1 | .navbarheader { 2 | display: flex; 3 | overflow-x: auto; 4 | gap: 10px; 5 | padding-bottom: 6px; 6 | scroll-behavior: smooth; 7 | 8 | &::-webkit-scrollbar { 9 | width: 10px; 10 | height: 10px; 11 | background-color: #dcdcdc; 12 | border-top-right-radius: 8px; 13 | border-bottom-right-radius: 8px; 14 | } 15 | 16 | &::-webkit-scrollbar-thumb { 17 | background-color: #1D7775; 18 | border-radius: 8px; 19 | } 20 | 21 | &__item { 22 | height: 30px; 23 | padding: 0 8px; 24 | display: grid; 25 | place-items: center; 26 | border-radius: 6px; 27 | color: #1D7775; 28 | font-weight: 600; 29 | transition: 0.2s ease color, background-color; 30 | cursor: pointer; 31 | 32 | &:hover { 33 | color: #13514f; 34 | } 35 | 36 | &:active { 37 | color: #0d3837; 38 | } 39 | 40 | &-selected { 41 | color: #FFFFFF !important; 42 | background-color: #1D7775; 43 | 44 | &:hover { 45 | background-color: #186462; 46 | } 47 | 48 | &:active { 49 | background-color: #13514f; 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/builder/components/select/Select.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 39 | 40 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-input/NavbarInput.ts: -------------------------------------------------------------------------------- 1 | import Button from "@/builder/modules/global/button/Button.vue" 2 | 3 | import { useDrag } from "@/builder/hooks/UseDrag" 4 | import { useMessages } from "@/builder/hooks/UseMessages"; 5 | 6 | enum ItemTypes { 7 | text = 'Text', 8 | number = 'Number', 9 | date = 'Date', 10 | time = 'Time', 11 | textArea = 'TextArea', 12 | select = 'Select', 13 | checkBox = 'CheckBox' 14 | } 15 | 16 | interface ButtonItem { 17 | id: ItemTypes; 18 | name: string; 19 | } 20 | 21 | export default { 22 | components: { 23 | Button 24 | }, 25 | setup() { 26 | const { onDragStart } = useDrag() 27 | const messages = useMessages('builderPage.inputField') 28 | 29 | const buttonList: ButtonItem[] = [ 30 | { id: ItemTypes.text, name: messages.textInput }, 31 | { id: ItemTypes.number, name: messages.numberInput }, 32 | { id: ItemTypes.date, name: messages.dateInput }, 33 | { id: ItemTypes.time, name: messages.timeInput }, 34 | { id: ItemTypes.textArea, name: messages.textAreaInput }, 35 | { id: ItemTypes.select, name: messages.selectInput }, 36 | { id: ItemTypes.checkBox, name: messages.checkBoxInput } 37 | ] 38 | 39 | return { 40 | onDragStart, 41 | buttonList 42 | } 43 | }, 44 | } -------------------------------------------------------------------------------- /src/builder/components/check-box/CheckBox.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 43 | 44 | -------------------------------------------------------------------------------- /src/builder/modules/global/switch/Switch.scss: -------------------------------------------------------------------------------- 1 | .switch { 2 | width: 100%; 3 | border-radius: 6px; 4 | display: flex; 5 | flex-direction: column; 6 | 7 | &__title { 8 | font-size: 14px; 9 | color: #5E5873; 10 | font-weight: 600; 11 | margin-bottom: 2px; 12 | } 13 | 14 | &__inner { 15 | width: 100%; 16 | display: flex; 17 | border-radius: 6px; 18 | height: 36px; 19 | padding: 0 4px; 20 | } 21 | 22 | &__area { 23 | width: 100%; 24 | height: 100%; 25 | display: flex; 26 | box-shadow: 0 0 2px rgb(29 119 117); 27 | border-radius: 6px; 28 | } 29 | 30 | &__item { 31 | flex: 1; 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | width: 100%; 36 | cursor: pointer; 37 | font-weight: 600; 38 | font-size: 14px; 39 | border-radius: 6px; 40 | transition: 0.2s ease background-color, color; 41 | 42 | &:first-child { 43 | border-top-left-radius: 6px; 44 | border-bottom-left-radius: 6px; 45 | } 46 | 47 | &:last-child { 48 | border-top-right-radius: 6px; 49 | border-bottom-right-radius: 6px; 50 | } 51 | } 52 | 53 | &__active { 54 | background-color: #1D7775; 55 | color: #FFFFFF; 56 | } 57 | } -------------------------------------------------------------------------------- /src/builder/pages/main/Main.ts: -------------------------------------------------------------------------------- 1 | import { inject, onMounted, watch } from "vue" 2 | import { CurrentPage, type AppState } from "../../interfaces/AppState" 3 | 4 | import Button from "@/builder/modules/global/button/Button.vue" 5 | import eventBus from "@/builder/utils/EventBus" 6 | import { useDrag } from "@/builder/hooks/UseDrag" 7 | import { useMessages } from "@/builder/hooks/UseMessages" 8 | 9 | export default { 10 | components: { 11 | Button 12 | }, 13 | setup() { 14 | const appState = inject('appState') as AppState 15 | 16 | const messages = useMessages('mainPage') 17 | 18 | const { setNewForm } = useDrag() 19 | 20 | const routeToBuilder = (formId?: number) => { 21 | if (formId) { 22 | let currentForm = undefined 23 | appState.options.formList.forEach((perForm) => { 24 | if (perForm.id == formId) currentForm = perForm 25 | }) 26 | if (currentForm) setNewForm(currentForm) 27 | } 28 | appState.currentPage = CurrentPage.builder 29 | } 30 | 31 | const deleteForm = (formId: number) => { 32 | eventBus.dispatch('onFormDelete', formId) 33 | } 34 | 35 | onMounted(() => { 36 | setNewForm() 37 | }) 38 | 39 | return { 40 | appState, 41 | routeToBuilder, 42 | deleteForm, 43 | messages 44 | } 45 | }, 46 | } -------------------------------------------------------------------------------- /src/builder/components/ComponentInterfaces.ts: -------------------------------------------------------------------------------- 1 | export enum Type { 2 | text = 'Text', 3 | number = 'Number', 4 | date = 'Date', 5 | time = 'Time' 6 | } 7 | 8 | export enum Size { 9 | half = 'Half', 10 | full = 'Full' 11 | } 12 | 13 | export interface InputStyle { 14 | color?: string; 15 | backgroundColor?: string; 16 | fontWeight?: number; 17 | fontSize?: string; 18 | height?: number; 19 | } 20 | 21 | export interface HeaderStyle { 22 | color?: string; 23 | backgroundColor?: string; 24 | fontWeight?: number; 25 | fontSize?: string; 26 | } 27 | 28 | export interface ValidationStyle { 29 | color?: string; 30 | backgroundColor?: string; 31 | fontWeight?: number; 32 | fontSize?: string; 33 | } 34 | 35 | export interface Style { 36 | input?: InputStyle; 37 | header?: HeaderStyle; 38 | validation?: ValidationStyle; 39 | } 40 | 41 | export interface Validation { 42 | enabled?: boolean; 43 | maxLength?: number; 44 | minLength?: number; 45 | min?: number; 46 | max?: number; 47 | maxDate?: string; 48 | minDate?: string; 49 | maxTime?: string; 50 | minTime?: string; 51 | } 52 | 53 | export interface Properties { 54 | startingText?: string; 55 | placeholder?: string; 56 | header?: string; 57 | size?: Size; 58 | } 59 | 60 | export interface ComponentProperties { 61 | type: Type; 62 | style: Style; 63 | properties: Properties; 64 | preventDefault: boolean; 65 | } 66 | -------------------------------------------------------------------------------- /src/builder/components/input/Input.ts: -------------------------------------------------------------------------------- 1 | import { useMessages } from "../../hooks/UseMessages"; 2 | import { onMounted, ref, watch, type PropType } from "vue"; 3 | import type { 4 | Type, 5 | Properties, 6 | Style, 7 | Validation, 8 | ComponentProperties, 9 | } from "../ComponentInterfaces"; 10 | 11 | export default { 12 | props: { 13 | type: { 14 | type: String as PropType, 15 | required: true, 16 | }, 17 | properties: { 18 | type: Object as PropType, 19 | required: false, 20 | }, 21 | style: { 22 | type: Object as PropType -------------------------------------------------------------------------------- /src/builder/hooks/UseMessages.ts: -------------------------------------------------------------------------------- 1 | import { inject, reactive } from "vue" 2 | 3 | import DefaultMessages from "../constants/DefaultMessages" 4 | import type { Options, AppState } from "../interfaces/AppState" 5 | import type { Messages } from "../interfaces/Messages" 6 | 7 | const customMessages = reactive({}) 8 | 9 | export const useMessages = (path?: string): any => { 10 | return path ? seperatePath(customMessages, path) : customMessages || DefaultMessages 11 | } 12 | 13 | export const setLocale = (messages:Messages) => { 14 | Object.assign(customMessages,messages) 15 | } 16 | const seperatePath = (messages: Messages, path: string): any => { 17 | const seperatedPath = path.includes('.') ? path.split('.') : path 18 | // @ts-ignore 19 | return typeof seperatedPath == 'object' ? calculatePathObject(messages, seperatedPath) : mergeWithDefault(messages[seperatedPath], DefaultMessages[seperatedPath]) 20 | } 21 | 22 | const calculatePathObject = (messages: Messages, path: string[], currentObject: any = undefined, defaultObject?: any): any => { 23 | // @ts-ignore 24 | const _currentObject = currentObject ? currentObject[path[0]] : messages[path[0]] 25 | // @ts-ignore 26 | const _defaultObject = defaultObject ? defaultObject[path[0]] : DefaultMessages[path[0]] 27 | path.shift() 28 | 29 | return path.length > 0 ? calculatePathObject(messages, path, _currentObject, _defaultObject) : mergeWithDefault(_currentObject, _defaultObject) 30 | } 31 | 32 | const mergeWithDefault = (currentObject: any, defaultObject: any) => { 33 | if (!currentObject) currentObject = {} 34 | if (!defaultObject) defaultObject = {} 35 | // @ts-ignore 36 | for (let defaultEntries of Object.entries(defaultObject)) { 37 | if (!currentObject[defaultEntries[0]]) currentObject[defaultEntries[0]] = defaultEntries[1] 38 | } 39 | return currentObject 40 | } -------------------------------------------------------------------------------- /src/Global.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #app { 4 | width: 100vw; 5 | height: 100vh; 6 | } 7 | 8 | * { 9 | margin: 0; 10 | padding: 0; 11 | box-sizing: border-box; 12 | font-family: 'Roboto', sans-serif; 13 | font-size: 14px; 14 | } 15 | 16 | .layout { 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | flex-direction: column; 21 | width: 100%; 22 | height: 100%; 23 | background-color: rgba(29, 119, 117,0.6); 24 | } 25 | 26 | .text { 27 | font-size: 20px; 28 | font-weight: 800; 29 | color: #1D7775; 30 | margin-bottom: 8px; 31 | text-shadow: 0 0 6px rgb(255 255 255 / 40%); 32 | } 33 | 34 | .builder { 35 | width: 80%; 36 | height: 600px; 37 | background-color: #FFFFFF; 38 | border-radius: 6px; 39 | } 40 | 41 | .parsers { 42 | position: fixed; 43 | top: 0; 44 | left: 0; 45 | width: 100vw; 46 | height: 100vh; 47 | background-color: rgba(0,0,0,0.4); 48 | display: grid; 49 | place-items: center; 50 | 51 | &__active { 52 | font-size: 14px; 53 | font-weight: 600; 54 | color: #FFFFFF; 55 | margin-bottom: 20px; 56 | text-shadow: 0 0 6px rgb(255 255 255 / 40%); 57 | background: #1D7775; 58 | padding: 6px 10px; 59 | border-radius: 6px; 60 | cursor: pointer; 61 | transition: 0.2s ease background-color; 62 | 63 | &:hover { 64 | background: #155b5a; 65 | } 66 | 67 | } 68 | 69 | &__btn { 70 | position: absolute; 71 | right: 20px; 72 | top: 20px; 73 | font-weight: 800; 74 | color: #1D7775; 75 | cursor: pointer; 76 | background: white; 77 | padding: 6px 8px; 78 | border-radius: 8px; 79 | transition: 0.2s ease all; 80 | font-size: 16px; 81 | 82 | &:hover { 83 | background: rgb(180, 180, 180); 84 | } 85 | } 86 | 87 | &__area { 88 | width: 400px; 89 | padding: 8px; 90 | background: #fff; 91 | border-radius: 6px; 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-property/NavbarProperty.ts: -------------------------------------------------------------------------------- 1 | import { watch, ref } from "vue"; 2 | 3 | import Input from "../../../../components/input/Input.vue"; 4 | import Switch from "../../../../modules/global/switch/Switch.vue"; 5 | import Select from "../../../../components/select/Select.vue"; 6 | import OptionList from "../../../../components/option-list/OptionList.vue"; 7 | 8 | import { useDrag } from "../../../../hooks/UseDrag"; 9 | import { useMessages } from "../../../../hooks/UseMessages"; 10 | 11 | export enum Type { 12 | text = "Text", 13 | number = "Number", 14 | date = "Date", 15 | time = "Time", 16 | select = "Select", 17 | checkBox = "CheckBox", 18 | } 19 | 20 | export enum Size { 21 | half = "Half", 22 | full = "Full", 23 | } 24 | 25 | interface Value { 26 | id: number; 27 | value: string; 28 | } 29 | 30 | interface InputValues { 31 | header?: string; 32 | placeholder?: string; 33 | startingText?: string; 34 | values?: Value[]; 35 | activeValue?: Value; 36 | size?: Size; 37 | } 38 | 39 | export default { 40 | components: { 41 | Input, 42 | Switch, 43 | Select, 44 | OptionList, 45 | }, 46 | setup() { 47 | const { state, setProperty, getProperties } = useDrag(); 48 | const messages = useMessages("builderPage.propertyField"); 49 | 50 | // @ts-ignore 51 | const inputValues = ref(getProperties()); 52 | 53 | // @ts-ignore 54 | watch(state, () => (inputValues.value = getProperties())); 55 | 56 | const setInputValue = (key: keyof InputValues, val: any) => { 57 | inputValues.value[key] = val; 58 | updateState(key, val); 59 | }; 60 | 61 | const updateState = (key: keyof InputValues, val: any) => { 62 | setProperty(key, val); 63 | }; 64 | 65 | return { 66 | state, 67 | inputValues, 68 | messages, 69 | setProperty, 70 | setInputValue, 71 | }; 72 | }, 73 | }; 74 | -------------------------------------------------------------------------------- /src/builder/components/select/Select.ts: -------------------------------------------------------------------------------- 1 | import { useMessages } from "../../hooks/UseMessages"; 2 | import { onMounted, ref, watch, type PropType } from "vue"; 3 | 4 | interface Validation { 5 | enabled: boolean; 6 | } 7 | 8 | interface Option { 9 | id: number; 10 | value: string; 11 | } 12 | 13 | interface ComponentProperties { 14 | properties: Properties; 15 | validation?: Validation; 16 | } 17 | 18 | interface Properties { 19 | header?: string; 20 | placeholder?: string; 21 | values?: Option[]; 22 | activeValue?: number; 23 | } 24 | 25 | export default { 26 | props: { 27 | properties: { 28 | type: Object as PropType, 29 | required: false, 30 | default: { 31 | header: undefined, 32 | placeholder: undefined, 33 | values: [], 34 | activeValue: undefined, 35 | }, 36 | }, 37 | validation: { 38 | type: Object as PropType, 39 | required: false, 40 | default: { enabled: false }, 41 | }, 42 | }, 43 | setup(props: ComponentProperties, { emit }: any) { 44 | const messages = useMessages("defaultInputs"); 45 | 46 | const activeValue = ref({ id: -1 }); 47 | 48 | watch(activeValue, () => emit("onChange", activeValue.value)); 49 | 50 | const onChange = (valueId: string) => { 51 | if (props.properties && props.properties.values) { 52 | props.properties.values.forEach((value: Option) => { 53 | if (String(value.id) == valueId) activeValue.value = { id: value.id }; 54 | }); 55 | } 56 | }; 57 | 58 | watch(props, () => { 59 | if (props.properties.activeValue) 60 | activeValue.value = { id: props.properties.activeValue }; 61 | }); 62 | 63 | onMounted(() => { 64 | if (props.properties.activeValue) { 65 | activeValue.value = { id: props.properties.activeValue }; 66 | } 67 | }); 68 | 69 | return { 70 | onChange, 71 | activeValue, 72 | messages, 73 | }; 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/form-area/form-layout/FormLayout.scss: -------------------------------------------------------------------------------- 1 | .formlayout { 2 | scroll-behavior: smooth; 3 | flex: 1; 4 | overflow: auto; 5 | position: relative; 6 | 7 | &__scroll { 8 | scroll-behavior: smooth; 9 | width: 100%; 10 | min-width: 530px; 11 | display: grid; 12 | grid-template-columns: repeat(2, 1fr); 13 | height: min-content; 14 | overflow: hidden; 15 | padding: 16px 0 80px 0; 16 | } 17 | 18 | &::-webkit-scrollbar { 19 | width: 10px; 20 | height: 10px; 21 | background-color: #dcdcdc; 22 | border-top-right-radius: 8px; 23 | border-bottom-right-radius: 8px; 24 | } 25 | 26 | &::-webkit-scrollbar-thumb { 27 | background-color: #1D7775; 28 | border-radius: 8px; 29 | } 30 | 31 | &__trash { 32 | position: sticky; 33 | bottom: 20px; 34 | left: 88%; 35 | display: grid; 36 | place-items: center; 37 | width: 64px; 38 | height: 64px; 39 | border-radius: 8px; 40 | background: rgb(29 119 117 / 60%); 41 | box-shadow: 1px 1px 6px rgb(29 119 117 / 80%); 42 | cursor: pointer; 43 | padding-bottom: 18px; 44 | 45 | &-img { 46 | width: 48px; 47 | height: 48px; 48 | } 49 | } 50 | 51 | &__noitem { 52 | width: 100%; 53 | height: 100%; 54 | display: flex; 55 | justify-content: center; 56 | align-items: center; 57 | flex-direction: column; 58 | 59 | &-icon { 60 | width: 25%; 61 | height: 25%; 62 | } 63 | 64 | &-text { 65 | font-weight: 600; 66 | font-size: 20px; 67 | color: #1D7775; 68 | } 69 | } 70 | } 71 | 72 | 73 | @media only screen and (max-width: 664px) { 74 | .formlayout { 75 | 76 | &__scroll { 77 | min-width: inherit; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/builder/pages/component-test/ComponentTest.ts: -------------------------------------------------------------------------------- 1 | import Input from "../../components/input/Input.vue" 2 | import TextArea from "../../components/text-area/TextArea.vue" 3 | 4 | export default { 5 | components: { 6 | Input, 7 | TextArea 8 | }, 9 | setup() { 10 | const inputTestData = [ 11 | { 12 | type: 'Text', 13 | properties: { header: 'Aloo', placeholder: 'Aloo...', startingText: 'xD', size: 'Full' }, 14 | style: { 15 | input: { 16 | backgroundColor: 'red', 17 | color: 'white', 18 | fontSize: '12px', 19 | fontWeight: 600, 20 | height: '70px' 21 | }, 22 | header: { 23 | color: 'blue' 24 | }, 25 | validation: { 26 | 27 | } 28 | }, 29 | validation: { 30 | enabled: false 31 | } 32 | }, 33 | { 34 | type: 'Number', 35 | properties: { header: 'Aloo', placeholder: 'Aloo...', startingText: '', size: 'Half' }, 36 | style: { 37 | input: { 38 | backgroundColor: 'red', 39 | color: 'white', 40 | fontSize: 12, 41 | fontWeight: 600, 42 | height: 70 43 | }, 44 | header: { 45 | color: 'blue' 46 | }, 47 | validation: { 48 | color: 'green' 49 | } 50 | }, 51 | validation: { 52 | enabled: true 53 | } 54 | }, 55 | 56 | ] 57 | 58 | return { 59 | inputTestData 60 | } 61 | }, 62 | } -------------------------------------------------------------------------------- /src/builder/assets/header/form.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/builder/components/option-list/OptionList.scss: -------------------------------------------------------------------------------- 1 | .option { 2 | display: flex; 3 | flex-direction: column; 4 | 5 | &__title { 6 | font-size: 14px; 7 | color: #5E5873; 8 | font-weight: 600; 9 | margin-bottom: 2px; 10 | } 11 | 12 | &__inner { 13 | display: flex; 14 | justify-content: space-between; 15 | margin-bottom: 2px; 16 | align-items: flex-end; 17 | gap: 6px; 18 | } 19 | 20 | &__item { 21 | display: flex; 22 | flex-direction: column; 23 | justify-content: space-between; 24 | padding-left: 6px; 25 | 26 | &-input { 27 | outline: none; 28 | flex: 1; 29 | border: 1px solid #5E5873; 30 | border-radius: 6px; 31 | margin-bottom: 2px; 32 | padding: 0 6px; 33 | margin-right: 6px; 34 | height: 24px; 35 | } 36 | 37 | &-btn { 38 | height: 24px; 39 | width: 30px; 40 | display: grid; 41 | place-items: center; 42 | font-weight: 600; 43 | color: #FFFFFF; 44 | background: #DC143C; 45 | border-radius: 6px; 46 | cursor: pointer; 47 | font-size: 12px; 48 | transition: 0.2s ease background-color; 49 | margin-bottom: 2px; 50 | 51 | &:hover { 52 | background-color: #be1234; 53 | } 54 | 55 | } 56 | } 57 | 58 | &__add { 59 | display: flex; 60 | justify-content: flex-end; 61 | 62 | &-btn { 63 | cursor: pointer; 64 | background-color: #1D7775; 65 | color: #FFFFFF; 66 | padding: 6px; 67 | border-radius: 6px; 68 | text-align: center; 69 | font-weight: 600; 70 | font-size: 14px; 71 | width: 50%; 72 | transition: 0.2s ease background-color; 73 | 74 | &:hover { 75 | background-color: #186462; 76 | } 77 | 78 | &:active { 79 | background-color: #13514f; 80 | transition: 0s ease background-color; 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/builder/interfaces/Messages.ts: -------------------------------------------------------------------------------- 1 | export interface Messages { 2 | mainPage?: MainPage; 3 | builderPage?: BuilderPage; 4 | defaultInputs?: DefaultInputs; 5 | errorList?: ErrorList; 6 | } 7 | 8 | interface ErrorList { 9 | headerRequired?: string; 10 | } 11 | 12 | interface MainPage { 13 | headerTitle?: string; 14 | headerDescription?: string; 15 | createFormButton?: string; 16 | yourForms?: string; 17 | notSetFormDescription?: string; 18 | updateFormButton?: string; 19 | deleteFormButton?: string; 20 | addFormButton?: string; 21 | haveZeroForm?: string; 22 | } 23 | 24 | interface BuilderPage { 25 | header?: Header; 26 | layout?: Layout; 27 | fieldNames?: FieldNames; 28 | inputField?: InputField; 29 | propertyField?: PropertyField; 30 | styleField?: StyleField; 31 | validationField?: ValidationField; 32 | } 33 | 34 | // Builder Page 35 | 36 | interface Header { 37 | formName?: string; 38 | formInputPlaceholder?: string; 39 | saveFormButton?: string; 40 | } 41 | 42 | interface Layout { 43 | underConstructor?: string; 44 | dragDrop?: string; 45 | updateButton?: string; 46 | deleteButton?: string; 47 | addHere?: string; 48 | } 49 | 50 | interface FieldNames { 51 | inputs?: string; 52 | properties?: string; 53 | styles?: string; 54 | validations?: string; 55 | } 56 | 57 | interface ValidationField {} 58 | 59 | interface StyleField {} 60 | 61 | interface PropertyField { 62 | titleTitle?: string; 63 | titlePlaceholder?: string; 64 | placeholderTitle?: string; 65 | placeholderPlaceholder?: string; 66 | startingTextTitle?: string; 67 | startingTextPlaceholder?: string; 68 | startingItemTitle?: string; 69 | startingItemPlaceholder?: string; 70 | inputSizeTitle?: string; 71 | inputSizeFull?: string; 72 | inputSizeHalf?: string; 73 | inputValuesTitle?: string; 74 | inputValuesItemTitle?: string; 75 | inputValuesItemPlaceholder?: string; 76 | inputValuesDeleteButton?: string; 77 | inputValuesAddButton?: string; 78 | notSelectAnyItem?: string; 79 | } 80 | 81 | interface InputField { 82 | textInput?: string; 83 | numberInput?: string; 84 | dateInput?: string; 85 | timeInput?: string; 86 | textAreaInput?: string; 87 | selectInput?: string; 88 | checkBoxInput?: string; 89 | } 90 | 91 | // Inputs 92 | 93 | interface DefaultInputs { 94 | title?: string; 95 | placeholder?: string; 96 | validation?: string; 97 | checkboxPlaceholder?: string; 98 | } 99 | -------------------------------------------------------------------------------- /src/builder/pages/main/Main.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 64 | 65 | -------------------------------------------------------------------------------- /src/builder/constants/DefaultMessages.ts: -------------------------------------------------------------------------------- 1 | import type { Messages } from "../interfaces/Messages"; 2 | 3 | const messages: Messages = { 4 | mainPage: { 5 | headerTitle: "Welcome to form builder", 6 | headerDescription: "You can create form how you want!", 7 | createFormButton: "Create New One", 8 | yourForms: "Your Forms", 9 | haveZeroForm: "You have 0 form!", 10 | notSetFormDescription: "Not set any description", 11 | updateFormButton: "Edit", 12 | deleteFormButton: "Delete", 13 | addFormButton: "Add New Item", 14 | }, 15 | builderPage: { 16 | header: { 17 | formName: "Form Name", 18 | formInputPlaceholder: "Please write form name...", 19 | saveFormButton: "Save Current Form", 20 | }, 21 | layout: { 22 | underConstructor: "Under The Constructor", 23 | dragDrop: "Drag & Drop Here", 24 | updateButton: "Edit", 25 | deleteButton: "Delete", 26 | addHere: "Add Here", 27 | }, 28 | fieldNames: { 29 | inputs: "Inputs", 30 | properties: "Properties", 31 | styles: "Styles", 32 | validations: "Validation", 33 | }, 34 | inputField: { 35 | textInput: "Text", 36 | numberInput: "Number", 37 | dateInput: "Date", 38 | timeInput: "Time", 39 | textAreaInput: "Text Area", 40 | selectInput: "Select", 41 | checkBoxInput: "Check Box", 42 | }, 43 | propertyField: { 44 | titleTitle: "Title", 45 | titlePlaceholder: "Title...", 46 | placeholderTitle: "Placeholder", 47 | placeholderPlaceholder: "Placeholder...", 48 | startingTextTitle: "Starting Text", 49 | startingTextPlaceholder: "Starting Text...", 50 | startingItemTitle: "Starting Item", 51 | startingItemPlaceholder: "Starting Item...", 52 | inputSizeTitle: "Input Size", 53 | inputSizeFull: "Full", 54 | inputSizeHalf: "Half", 55 | inputValuesTitle: "Input Values", 56 | inputValuesItemTitle: "Value", 57 | inputValuesItemPlaceholder: "Value...", 58 | inputValuesDeleteButton: "Del", 59 | inputValuesAddButton: "Add New Value", 60 | notSelectAnyItem: "You Are Not Selected Any Item.", 61 | }, 62 | styleField: {}, 63 | validationField: {}, 64 | }, 65 | defaultInputs: { 66 | title: "Header Text", 67 | placeholder: "Placeholder...", 68 | checkboxPlaceholder: "Any option is not set", 69 | validation: "Under the constructor", 70 | }, 71 | errorList: { 72 | headerRequired: "Header name is required!", 73 | }, 74 | }; 75 | 76 | export default messages; 77 | -------------------------------------------------------------------------------- /src/builder/modules/form-builder/navbar/navbar-layout/NavbarLayout.ts: -------------------------------------------------------------------------------- 1 | import { useDrag } from "@/builder/hooks/UseDrag"; 2 | import { useMessages } from "@/builder/hooks/UseMessages"; 3 | import eventBus from "@/builder/utils/EventBus"; 4 | import { computed } from "@vue/reactivity"; 5 | import { onMounted, onUnmounted, ref } from "vue"; 6 | 7 | import NavbarHeader from "../navbar-header/NavbarHeader.vue" 8 | import NavbarInput from "../navbar-input/NavbarInput.vue"; 9 | import NavbarProperty from "../navbar-property/NavbarProperty.vue"; 10 | import NavbarStyle from "../navbar-style/NavbarStyle.vue"; 11 | import NavbarValidation from "../navbar-validation/NavbarValidation.vue"; 12 | 13 | enum NavbarHeaderNames { 14 | inputs = 'Inputs', 15 | property = 'Property', 16 | style = 'Style', 17 | validation = 'Validation' 18 | } 19 | 20 | interface NavbarHeader { 21 | type: NavbarHeaderNames; 22 | name: string; 23 | } 24 | 25 | export default { 26 | components: { 27 | NavbarHeader, 28 | NavbarInput, 29 | NavbarProperty, 30 | NavbarStyle, 31 | NavbarValidation 32 | }, 33 | setup() { 34 | const messages = useMessages('builderPage.fieldNames') 35 | 36 | const { state } = useDrag() 37 | 38 | const navbarHeaders = computed(() => { 39 | const headers = [ 40 | { type: NavbarHeaderNames.inputs, name: messages.inputs }, 41 | { type: NavbarHeaderNames.property, name: messages.properties }, 42 | ] 43 | 44 | if (state.currentForm == undefined || state.currentForm?.canStyleChangable == true) headers.push({ type: NavbarHeaderNames.style, name: messages.styles }) 45 | if (state.currentForm == undefined || state.currentForm?.canValidationChangable == true) headers.push({ type: NavbarHeaderNames.validation, name: messages.validations }) 46 | 47 | return headers 48 | }) 49 | 50 | const selectedHeaderItem = ref(NavbarHeaderNames.inputs) 51 | 52 | const changeActiveNavbar = (item: NavbarHeaderNames) => { 53 | selectedHeaderItem.value = item 54 | } 55 | 56 | onMounted(() => { 57 | eventBus.on('changeActiveNavbar', changeActiveNavbar) 58 | }) 59 | 60 | onUnmounted(() => { 61 | eventBus.remove('changeActiveNavbar', changeActiveNavbar) 62 | }) 63 | 64 | return { 65 | navbarHeaders, 66 | NavbarHeaderNames, 67 | selectedHeaderItem, 68 | changeActiveNavbar 69 | } 70 | }, 71 | } -------------------------------------------------------------------------------- /src/builder/modules/parser/Parser.vue: -------------------------------------------------------------------------------- 1 |