├── frontend ├── .browserslistrc ├── babel.config.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ ├── logo.png │ │ ├── scripts │ │ │ └── evan-custom.js │ │ ├── icons │ │ │ ├── search-icon.svg │ │ │ ├── column-icon.svg │ │ │ ├── more-vertical-icon.svg │ │ │ ├── add-square-icon.svg │ │ │ ├── info-icons.svg │ │ │ ├── add-icon.svg │ │ │ ├── handler-icon.svg │ │ │ ├── trash-icon.svg │ │ │ ├── spacer-icon.svg │ │ │ ├── eye-icon-white.svg │ │ │ ├── image-icon.svg │ │ │ ├── settings-icon.svg │ │ │ ├── carousel-icon.svg │ │ │ ├── progressbar-icon.svg │ │ │ ├── lists-icon.svg │ │ │ ├── heading-icon.svg │ │ │ ├── button-icon.svg │ │ │ ├── gallery-icon.svg │ │ │ ├── text-icon.svg │ │ │ ├── builder-icon-white.svg │ │ │ └── social-icon.svg │ │ ├── styles │ │ │ └── evan-custom.css │ │ ├── dummy-section-data.json │ │ └── builder-data.json │ ├── views │ │ ├── Home.vue │ │ ├── About.vue │ │ └── Builder.vue │ ├── store │ │ ├── index.js │ │ └── modules │ │ │ ├── layouts.js │ │ │ ├── builder-sidebar.js │ │ │ └── page.js │ ├── components │ │ ├── builder │ │ │ ├── BuilderMain.vue │ │ │ ├── BuilderSidebarSearchbar.vue │ │ │ ├── BuilderTopbar.vue │ │ │ ├── BuilderSidebar.vue │ │ │ ├── BuilderSidebarElements.vue │ │ │ ├── BuilderSidebarSettings.vue │ │ │ └── BuilderSidebarTopnav.vue │ │ ├── widgets │ │ │ ├── common │ │ │ │ ├── SpacerWidget.vue │ │ │ │ ├── ImageWidget.vue │ │ │ │ ├── TextWidget.vue │ │ │ │ ├── HeadingWidget.vue │ │ │ │ ├── ButtonWidget.vue │ │ │ │ └── ListWidget.vue │ │ │ ├── WidgetEditText.vue │ │ │ └── SettingsWidget.vue │ │ ├── utility │ │ │ ├── TextEdit.vue │ │ │ ├── TextAlign.vue │ │ │ ├── VerticalContent.vue │ │ │ ├── ColorPicker.vue │ │ │ ├── HorizontalContent.vue │ │ │ ├── WidthType.vue │ │ │ ├── RangeNumber.vue │ │ │ ├── UploadImage.vue │ │ │ └── ListText.vue │ │ ├── TopNav.vue │ │ ├── section │ │ │ ├── SectionAdd.vue │ │ │ ├── SectionList.vue │ │ │ └── SectionItem.vue │ │ ├── column │ │ │ ├── ColumnSettings.vue │ │ │ ├── ColumnList.vue │ │ │ ├── ColumnItem.vue │ │ │ └── ColumnLayout.vue │ │ └── element │ │ │ ├── ElementList.vue │ │ │ └── ElementItem.vue │ ├── main.js │ ├── router │ │ └── index.js │ └── App.vue ├── .gitignore ├── .eslintrc.js ├── README.md └── package.json └── README.md /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eevan7a9/webpage-builder-elementor/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eevan7a9/webpage-builder-elementor/HEAD/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webpage-builder 2 | 3 | An attempt to mimic Elementor's page building functionality 4 | 5 | **[View the demo](https://elementor.netlify.app/)** -------------------------------------------------------------------------------- /frontend/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /frontend/src/assets/scripts/evan-custom.js: -------------------------------------------------------------------------------- 1 | // get latest/highest/largest of objects id from array of objects 2 | export const getLatestId = array => 3 | Math.max.apply( 4 | Math, 5 | array.map(item => item.id) 6 | ); 7 | 8 | export const toTimestamp = strDate => { 9 | var datum = Date.parse(strDate); 10 | return datum / 1000; 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/search-icon.svg: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import builderSidebar from "./modules/builder-sidebar.js"; 4 | import page from "./modules/page.js"; 5 | import layouts from "./modules/layouts.js"; 6 | 7 | Vue.use(Vuex); 8 | 9 | export default new Vuex.Store({ 10 | modules: { 11 | builderSidebar, 12 | page, 13 | layouts 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /frontend/src/store/modules/layouts.js: -------------------------------------------------------------------------------- 1 | import builderData from "@/assets/builder-data.json"; 2 | 3 | const state = { 4 | layoutChoices: builderData.layouts 5 | }; 6 | const getters = { 7 | getLayouts: state => state.layoutChoices 8 | }; 9 | const actions = {}; 10 | const mutations = {}; 11 | 12 | export default { 13 | state, 14 | getters, 15 | actions, 16 | mutations 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "@vue/prettier"], 7 | rules: { 8 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 9 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" 10 | }, 11 | parserOptions: { 12 | parser: "babel-eslint" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/column-icon.svg: -------------------------------------------------------------------------------- 1 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/more-vertical-icon.svg: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/add-square-icon.svg: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/info-icons.svg: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/components/builder/BuilderMain.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/widgets/common/SpacerWidget.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # frontend 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/add-icon.svg: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/src/assets/styles/evan-custom.css: -------------------------------------------------------------------------------- 1 | /* 2 | VueDraggable styles start 3 | */ 4 | .flip-list-move { 5 | transition: transform 0.5s; 6 | } 7 | .flip-list-enter-active, 8 | .flip-list-leave-active { 9 | transition: all 1s; 10 | } 11 | .flip-list-enter, .flip-list-leave-to /* .list-leave-active below version 2.1.8 */ { 12 | opacity: 0; 13 | transform: translateY(30px); 14 | } 15 | 16 | .no-move { 17 | transition: transform 0s; 18 | } 19 | .ghost { 20 | opacity: 0.5; 21 | background: #c8ebfb; 22 | } 23 | /* 24 | VueDraggable styles ends 25 | */ 26 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/handler-icon.svg: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | frontend 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import store from "./store"; 5 | import BootstrapVue from "bootstrap-vue"; 6 | import "bootstrap/dist/css/bootstrap.css"; 7 | import "bootstrap-vue/dist/bootstrap-vue.css"; 8 | import "@/assets/styles/evan-custom.css"; 9 | 10 | import VueSweetalert2 from "vue-sweetalert2"; 11 | 12 | import "sweetalert2/dist/sweetalert2.min.css"; 13 | 14 | Vue.use(VueSweetalert2); 15 | Vue.use(BootstrapVue); 16 | Vue.config.productionTip = false; 17 | 18 | new Vue({ 19 | router, 20 | store, 21 | render: h => h(App) 22 | }).$mount("#app"); 23 | -------------------------------------------------------------------------------- /frontend/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 17 | 25 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/trash-icon.svg: -------------------------------------------------------------------------------- 1 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/utility/TextEdit.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/spacer-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | import Home from "../views/Home.vue"; 4 | 5 | Vue.use(VueRouter); 6 | 7 | const routes = [ 8 | { 9 | path: "/", 10 | name: "builder", 11 | component: () => import("../views/Builder.vue") 12 | }, 13 | { 14 | path: "/home", 15 | name: "home", 16 | component: Home 17 | }, 18 | { 19 | path: "/about", 20 | name: "about", 21 | // route level code-splitting 22 | // this generates a separate chunk (about.[hash].js) for this route 23 | // which is lazy-loaded when the route is visited. 24 | component: () => 25 | import(/* webpackChunkName: "about" */ "../views/About.vue") 26 | } 27 | ]; 28 | 29 | const router = new VueRouter({ 30 | routes 31 | }); 32 | 33 | export default router; 34 | -------------------------------------------------------------------------------- /frontend/src/components/widgets/common/ImageWidget.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/eye-icon-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/utility/TextAlign.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 36 | -------------------------------------------------------------------------------- /frontend/src/components/utility/VerticalContent.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 36 | -------------------------------------------------------------------------------- /frontend/src/components/utility/ColorPicker.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /frontend/src/components/utility/HorizontalContent.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 36 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 15 | 46 | -------------------------------------------------------------------------------- /frontend/src/components/utility/WidthType.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 42 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "bootstrap": "^4.4.1", 12 | "bootstrap-vue": "^2.1.0", 13 | "core-js": "^3.4.4", 14 | "vue": "^2.6.11", 15 | "vue-color": "^2.7.0", 16 | "vue-router": "^3.1.3", 17 | "vue-sweetalert2": "^3.0.1", 18 | "vuedraggable": "^2.23.2", 19 | "vuex": "^3.1.2" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "^4.1.0", 23 | "@vue/cli-plugin-eslint": "^4.1.0", 24 | "@vue/cli-service": "^4.1.0", 25 | "@vue/eslint-config-prettier": "^5.0.0", 26 | "babel-eslint": "^10.0.3", 27 | "eslint": "^5.16.0", 28 | "eslint-plugin-prettier": "^3.1.1", 29 | "eslint-plugin-vue": "^5.0.0", 30 | "node-sass": "^4.14.1", 31 | "prettier": "^1.19.1", 32 | "sass-loader": "^8.0.0", 33 | "vue-template-compiler": "^2.6.10" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/image-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/settings-icon.svg: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/carousel-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/utility/RangeNumber.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /frontend/src/components/TopNav.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 32 | 33 | 49 | -------------------------------------------------------------------------------- /frontend/src/components/builder/BuilderSidebarSearchbar.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 39 | 40 | 57 | -------------------------------------------------------------------------------- /frontend/src/components/builder/BuilderTopbar.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 37 | 38 | 49 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/progressbar-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/views/Builder.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 38 | 39 | 55 | -------------------------------------------------------------------------------- /frontend/src/components/utility/UploadImage.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /frontend/src/components/widgets/common/TextWidget.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /frontend/src/components/widgets/common/HeadingWidget.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/lists-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/section/SectionAdd.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 30 | 31 | 61 | -------------------------------------------------------------------------------- /frontend/src/components/section/SectionList.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /frontend/src/components/widgets/common/ButtonWidget.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /frontend/src/components/column/ColumnSettings.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/heading-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/column/ColumnList.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /frontend/src/components/builder/BuilderSidebar.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 34 | 35 | 69 | -------------------------------------------------------------------------------- /frontend/src/components/widgets/common/ListWidget.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/button-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/gallery-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/utility/ListText.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /frontend/src/components/builder/BuilderSidebarElements.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 54 | 55 | 83 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/text-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/builder/BuilderSidebarSettings.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /frontend/src/components/builder/BuilderSidebarTopnav.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 56 | 57 | 92 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/builder-icon-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/store/modules/builder-sidebar.js: -------------------------------------------------------------------------------- 1 | import builderData from "@/assets/builder-data.json"; 2 | 3 | const state = { 4 | sidebarStatus: true, 5 | builderSidebarTab: "elements", // sidebar elements | styles 6 | widgets: JSON.parse(JSON.stringify(builderData.widgets)), 7 | settings: { 8 | content: false, 9 | tabOpen: 0, 10 | widget: {}, 11 | column: {} 12 | } 13 | }; 14 | const getters = { 15 | isSidebarOpen: state => state.sidebarStatus, 16 | getBuilderSidebar: state => state.builderSidebarTab, 17 | getWidgets: state => state.widgets, 18 | getSettings: state => state.settings 19 | }; 20 | const actions = { 21 | toggleBuilderSidebar: ({ commit }) => { 22 | commit("setSidebar"); 23 | }, 24 | toggleSidebarTab: ({ commit }, value) => { 25 | commit("setBuilderSidebar", value); 26 | }, 27 | fetchWidgets: ({ commit }, name) => { 28 | if (name) { 29 | const common = JSON.parse( 30 | JSON.stringify(builderData.widgets.common) 31 | ).filter(widget => 32 | widget.name.toLowerCase().includes(name.toLowerCase()) 33 | ); 34 | const general = JSON.parse( 35 | JSON.stringify(builderData.widgets.general) 36 | ).filter(widget => 37 | widget.name.toLowerCase().includes(name.toLowerCase()) 38 | ); 39 | commit("setWidgets", { common: common, general: general }); 40 | } else { 41 | commit("setWidgets", builderData.widgets); 42 | } 43 | }, 44 | selectWidget: ({ commit }, { widget, column }) => { 45 | commit("setSelectedWidget", { widget: widget, column: column }); 46 | }, 47 | selectColumn: ({ commit }, column) => { 48 | commit("setSelectedColumn", column); 49 | } 50 | }; 51 | const mutations = { 52 | setSidebar: state => (state.sidebarStatus = !state.sidebarStatus), 53 | setBuilderSidebar: (state, tab) => (state.builderSidebarTab = tab), 54 | setWidgets: (state, widgets) => { 55 | state.widgets.common = widgets.common; 56 | state.widgets.general = widgets.general; 57 | }, 58 | setSelectedWidget: (state, data) => { 59 | if (data.widget || data.column) { 60 | state.settings.widget = data.widget; 61 | state.settings.column = data.column; 62 | state.settings.tabOpen = 0; 63 | state.settings.content = true; 64 | } else { 65 | state.settings.widget = {}; 66 | state.settings.column = {}; 67 | state.settings.content = false; 68 | } 69 | }, 70 | setSelectedColumn: (state, column) => { 71 | state.settings.column = column; 72 | state.settings.widget = {}; 73 | if (column.id) { 74 | state.settings.content = true; 75 | state.settings.tabOpen = 1; 76 | } else { 77 | state.settings.content = false; 78 | } 79 | } 80 | }; 81 | 82 | export default { 83 | state, 84 | getters, 85 | actions, 86 | mutations 87 | }; 88 | -------------------------------------------------------------------------------- /frontend/src/components/widgets/WidgetEditText.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 94 | 95 | 111 | -------------------------------------------------------------------------------- /frontend/src/components/element/ElementList.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 95 | 96 | 111 | -------------------------------------------------------------------------------- /frontend/src/components/column/ColumnItem.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 81 | 82 | 140 | -------------------------------------------------------------------------------- /frontend/src/components/section/SectionItem.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 87 | 88 | 140 | -------------------------------------------------------------------------------- /frontend/src/components/column/ColumnLayout.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 79 | 80 | 147 | -------------------------------------------------------------------------------- /frontend/src/components/widgets/SettingsWidget.vue: -------------------------------------------------------------------------------- 1 | 106 | 107 | 138 | 139 | 148 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/social-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/element/ElementItem.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 118 | 119 | 184 | -------------------------------------------------------------------------------- /frontend/src/store/modules/page.js: -------------------------------------------------------------------------------- 1 | import { getLatestId, toTimestamp } from "@/assets/scripts/evan-custom.js"; 2 | import dummySectionData from "@/assets/dummy-section-data.json"; 3 | 4 | const state = { 5 | sections: dummySectionData.sections 6 | }; 7 | 8 | const getters = { 9 | getSections: state => state.sections 10 | }; 11 | 12 | const actions = { 13 | addSection: ({ commit, state }) => { 14 | // we get the id of the highest/latest id in sections 15 | let latestSectionId = getLatestId(state.sections); 16 | latestSectionId = latestSectionId > 0 ? latestSectionId + 1 : 1; 17 | const newSection = { 18 | id: latestSectionId, 19 | columns: [] 20 | }; 21 | commit("insertSection", newSection); 22 | }, 23 | deleteSection: ({ commit }, id) => { 24 | commit("removeSection", id); 25 | }, 26 | updateSections: ({ commit }, value) => { 27 | commit("setSections", value); 28 | }, 29 | addSectionColumns: ({ commit }, { layout, sectionId }) => { 30 | let idCounter = 1; 31 | layout.columns.forEach(col => { 32 | col.id = toTimestamp(new Date()) + idCounter; 33 | idCounter++; 34 | }); 35 | commit("setSectionColumns", { id: sectionId, sectionLayout: layout }); 36 | }, 37 | 38 | // Sections Columns ************************************* 39 | 40 | updateColumns: ({ commit }, { column, sectionId }) => { 41 | commit("setColumns", { item: column, sectionId: sectionId }); 42 | }, 43 | deleteColumnContent: ({ commit }, { columnId, sectionId }) => { 44 | commit("removeColumnContent", { 45 | columnId: columnId, 46 | sectionId: sectionId 47 | }); 48 | }, 49 | addColumn: ({ commit }, { column, sectionId }) => { 50 | commit("insertColumn", { 51 | column: column, 52 | sectionId: sectionId 53 | }); 54 | }, 55 | 56 | // Sections Elements ************************************* 57 | 58 | updateElements: ({ commit }, { elements, columnId, sectionId }) => { 59 | // we find the newly added element 60 | const foundElement = elements.find(el => el.new == true); 61 | if (foundElement) { 62 | foundElement.id = toTimestamp(new Date()); // assign new unique ID 63 | foundElement.new = false; // element is now not new 64 | } 65 | commit("setElements", { 66 | item: elements, 67 | columnId: columnId, 68 | sectionId: sectionId 69 | }); 70 | }, 71 | deleteElements: ({ commit }, { elementId, columnId, sectionId }) => { 72 | commit("removeElement", { 73 | elementId: elementId, 74 | columnId: columnId, 75 | sectionId: sectionId 76 | }); 77 | } 78 | }; 79 | 80 | const mutations = { 81 | // SECTION STARTS 82 | setSections: (state, sections) => (state.sections = sections), 83 | insertSection: (state, section) => state.sections.push(section), 84 | removeSection: (state, id) => 85 | (state.sections = state.sections.filter(section => section.id != id)), 86 | setSectionColumns: (state, section) => { 87 | const foundSection = state.sections.find(sec => sec.id == section.id); 88 | if (foundSection) { 89 | foundSection.label = section.sectionLayout.label; 90 | foundSection.layout = section.sectionLayout.layout; 91 | foundSection.columns = section.sectionLayout.columns; 92 | } 93 | }, 94 | // COLUMNS STARTS 95 | setColumns: (state, column) => { 96 | const foundSection = state.sections.find(sec => sec.id == column.sectionId); 97 | if (foundSection) { 98 | foundSection.columns = column.item; 99 | } 100 | }, 101 | removeColumnContent: (state, data) => { 102 | const foundSection = state.sections.find(sec => sec.id == data.sectionId); 103 | if (foundSection) { 104 | if (foundSection.layout) { 105 | const foundColumn = foundSection.columns.find( 106 | col => col.id == data.columnId 107 | ); 108 | if (foundColumn) { 109 | foundColumn.elements = []; 110 | } 111 | } else { 112 | foundSection.columns = foundSection.columns.filter( 113 | col => col.id != data.columnId 114 | ); 115 | } 116 | } 117 | }, 118 | insertColumn: (state, data) => { 119 | const foundSection = state.sections.find(sec => sec.id == data.sectionId); 120 | if (foundSection) { 121 | foundSection.columns.push(data.column); 122 | } 123 | }, 124 | // ElEMENTSS START 125 | setElements: (state, element) => { 126 | const foundSection = state.sections.find( 127 | sec => sec.id == element.sectionId 128 | ); 129 | if (foundSection) { 130 | const foundColumn = foundSection.columns.find( 131 | col => col.id == element.columnId 132 | ); 133 | if (foundColumn) { 134 | foundColumn.elements = element.item; 135 | } 136 | } 137 | }, 138 | removeElement: (state, data) => { 139 | const foundSection = state.sections.find(sec => sec.id == data.sectionId); 140 | if (foundSection) { 141 | const foundColumn = foundSection.columns.find( 142 | col => col.id == data.columnId 143 | ); 144 | if (foundColumn) { 145 | foundColumn.elements = foundColumn.elements.filter( 146 | el => el.id != data.elementId 147 | ); 148 | } 149 | } 150 | } 151 | }; 152 | 153 | export default { 154 | state, 155 | getters, 156 | actions, 157 | mutations 158 | }; 159 | -------------------------------------------------------------------------------- /frontend/src/assets/dummy-section-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": [ 3 | { 4 | "id": 1, 5 | "columns": [ 6 | { 7 | "id": 1579850578, 8 | "grid": null, 9 | "style": { 10 | "fill": true, 11 | "y": "start", 12 | "x": "center", 13 | "background": "#000000", 14 | "padding": "0" 15 | }, 16 | "elements": [ 17 | { 18 | "id": 1579850724, 19 | "name": "Image", 20 | "widget": "ImageWidget", 21 | "new": false, 22 | "icon": "image-icon.svg", 23 | "content": { 24 | "src": "https://via.placeholder.com/350x350", 25 | "style": { 26 | "width": "400px", 27 | "borderRadius": "0", 28 | "padding": "2", 29 | "margin": "2", 30 | "horizontal": "center" 31 | } 32 | } 33 | }, 34 | { 35 | "id": 1579850728, 36 | "name": "Text Editor", 37 | "widget": "TextWidget", 38 | "new": false, 39 | "icon": "text-icon.svg", 40 | "content": { 41 | "text": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...", 42 | "style": { 43 | "fontSize": "16", 44 | "fontWeight": "400", 45 | "textAlign": "center", 46 | "borderRadius": "0", 47 | "color": "#FBFBFB", 48 | "background": "transparent", 49 | "padding": "10", 50 | "margin": "2" 51 | } 52 | } 53 | } 54 | ] 55 | }, 56 | { 57 | "id": 1579850579, 58 | "grid": null, 59 | "style": { 60 | "fill": false, 61 | "y": "start", 62 | "x": "center", 63 | "background": "none", 64 | "padding": "0" 65 | }, 66 | "elements": [ 67 | { 68 | "id": 1579850583, 69 | "name": "Heading", 70 | "widget": "HeadingWidget", 71 | "new": false, 72 | "icon": "heading-icon.svg", 73 | "content": { 74 | "text": "What is This?\n", 75 | "style": { 76 | "fontSize": "40", 77 | "fontWeight": "900", 78 | "textAlign": "center", 79 | "borderRadius": "0", 80 | "color": "#FFFFFF", 81 | "background": "#000000", 82 | "padding": "10", 83 | "margin": "2" 84 | } 85 | } 86 | }, 87 | { 88 | "id": 1579850604, 89 | "name": "Text Editor", 90 | "widget": "TextWidget", 91 | "new": false, 92 | "icon": "text-icon.svg", 93 | "content": { 94 | "text": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...", 95 | "style": { 96 | "fontSize": "16", 97 | "fontWeight": "400", 98 | "textAlign": "center", 99 | "borderRadius": "0", 100 | "color": "#333", 101 | "background": "transparent", 102 | "padding": "10", 103 | "margin": "2" 104 | } 105 | } 106 | }, 107 | { 108 | "id": 1579850681, 109 | "name": "List", 110 | "widget": "ListWidget", 111 | "new": false, 112 | "icon": "lists-icon.svg", 113 | "content": { 114 | "title": "Phasellus eu risus fringilla, sodales odio at, iaculis ex.", 115 | "list": [ 116 | { 117 | "id": 1, 118 | "text": "Lorem ipsum dolor sit amet", 119 | "class": "" 120 | }, 121 | { 122 | "id": 2, 123 | "text": "In ut nulla posuere, euismod sapien at", 124 | "class": "" 125 | } 126 | ], 127 | "style": { 128 | "fontSize": "16", 129 | "fontWeight": "600", 130 | "textAlign": "left", 131 | "borderRadius": "0", 132 | "color": "#333", 133 | "background": "transparent", 134 | "padding": "10", 135 | "margin": "2", 136 | "listStyle": "none", 137 | "horizontal": "center" 138 | } 139 | } 140 | }, 141 | { 142 | "id": 1579850685, 143 | "name": "Button", 144 | "widget": "ButtonWidget", 145 | "new": false, 146 | "icon": "button-icon.svg", 147 | "content": { 148 | "text": "Subscribe", 149 | "link": "https://github.com/eevan7a9/", 150 | "style": { 151 | "fontSize": "16", 152 | "fontWeight": "600", 153 | "borderRadius": "24", 154 | "color": "#FFFFFF", 155 | "background": "#E60009", 156 | "padding": "26", 157 | "margin": "2", 158 | "horizontal": "center" 159 | } 160 | } 161 | } 162 | ] 163 | }, 164 | { 165 | "id": 1579850581, 166 | "grid": null, 167 | "style": { 168 | "fill": false, 169 | "y": "center", 170 | "x": "center", 171 | "background": "none", 172 | "padding": "0" 173 | }, 174 | "elements": [ 175 | { 176 | "id": 1579850630, 177 | "name": "Heading", 178 | "widget": "HeadingWidget", 179 | "new": false, 180 | "icon": "heading-icon.svg", 181 | "content": { 182 | "text": "I can do This:\n", 183 | "style": { 184 | "fontSize": "40", 185 | "fontWeight": "900", 186 | "textAlign": "center", 187 | "borderRadius": "0", 188 | "color": "#333", 189 | "background": "transparent", 190 | "padding": "10", 191 | "margin": "2" 192 | } 193 | } 194 | }, 195 | { 196 | "id": 1579850625, 197 | "name": "List", 198 | "widget": "ListWidget", 199 | "new": false, 200 | "icon": "lists-icon.svg", 201 | "content": { 202 | "title": "Phasellus eu risus fringilla, sodales odio at, iaculis ex.", 203 | "list": [ 204 | { 205 | "id": 1, 206 | "text": "Lorem ipsum dolor sit amet", 207 | "class": "" 208 | }, 209 | { 210 | "id": 2, 211 | "text": "In ut nulla posuere, euismod sapien at", 212 | "class": "" 213 | } 214 | ], 215 | "style": { 216 | "fontSize": "11", 217 | "fontWeight": "300", 218 | "textAlign": "left", 219 | "borderRadius": "0", 220 | "color": "#333", 221 | "background": "transparent", 222 | "padding": "27", 223 | "margin": "2", 224 | "listStyle": "none", 225 | "horizontal": "center" 226 | } 227 | } 228 | } 229 | ] 230 | } 231 | ], 232 | "label": "1 to 12", 233 | "layout": false 234 | }, 235 | { 236 | "id": 2, 237 | "columns": [ 238 | { 239 | "id": 1579850768, 240 | "grid": null, 241 | "style": { 242 | "fill": true, 243 | "y": "start", 244 | "x": "center", 245 | "background": "none", 246 | "padding": "0" 247 | }, 248 | "elements": [] 249 | }, 250 | { 251 | "id": 1579851136, 252 | "grid": null, 253 | "style": { 254 | "fill": true, 255 | "y": "center", 256 | "x": "center", 257 | "background": "none", 258 | "padding": "0" 259 | }, 260 | "elements": [ 261 | { 262 | "id": 1579851141, 263 | "name": "Heading", 264 | "widget": "HeadingWidget", 265 | "new": false, 266 | "icon": "heading-icon.svg", 267 | "content": { 268 | "text": "Heading Text", 269 | "style": { 270 | "fontSize": "40", 271 | "fontWeight": "900", 272 | "textAlign": "center", 273 | "borderRadius": "0", 274 | "color": "#333", 275 | "background": "transparent", 276 | "padding": "10", 277 | "margin": "2" 278 | } 279 | } 280 | }, 281 | { 282 | "id": 1579851138, 283 | "name": "Text Editor", 284 | "widget": "TextWidget", 285 | "new": false, 286 | "icon": "text-icon.svg", 287 | "content": { 288 | "text": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...", 289 | "style": { 290 | "fontSize": "16", 291 | "fontWeight": "400", 292 | "textAlign": "center", 293 | "borderRadius": "0", 294 | "color": "#333", 295 | "background": "transparent", 296 | "padding": "10", 297 | "margin": "2" 298 | } 299 | } 300 | } 301 | ] 302 | } 303 | ], 304 | "label": "1 to 12", 305 | "layout": false 306 | }, 307 | { 308 | "id": 3, 309 | "columns": [] 310 | } 311 | ] 312 | } -------------------------------------------------------------------------------- /frontend/src/assets/builder-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "widgets": { 3 | "common": [ 4 | { 5 | "id": 1, 6 | "name": "Heading", 7 | "widget": "HeadingWidget", 8 | "new": true, 9 | "icon": "heading-icon.svg", 10 | "content": { 11 | "text": "Heading Text", 12 | "style": { 13 | "fontSize": "40", 14 | "fontWeight": "900", 15 | "textAlign": "center", 16 | "borderRadius": "0", 17 | "color": "#333", 18 | "background": "transparent", 19 | "padding": "10", 20 | "margin": "2" 21 | } 22 | } 23 | }, 24 | { 25 | "id": 2, 26 | "name": "Text Editor", 27 | "widget": "TextWidget", 28 | "new": true, 29 | "icon": "text-icon.svg", 30 | "content": { 31 | "text": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...", 32 | "style": { 33 | "fontSize": "16", 34 | "fontWeight": "400", 35 | "textAlign": "center", 36 | "borderRadius": "0", 37 | "color": "#333", 38 | "background": "transparent", 39 | "padding": "10", 40 | "margin": "2" 41 | } 42 | } 43 | }, 44 | { 45 | "id": 3, 46 | "name": "List", 47 | "widget": "ListWidget", 48 | "new": true, 49 | "icon": "lists-icon.svg", 50 | "content": { 51 | "title": "Phasellus eu risus fringilla, sodales odio at, iaculis ex.", 52 | "list": [ 53 | { 54 | "id": 1, 55 | "text": "Lorem ipsum dolor sit amet", 56 | "class": "" 57 | }, 58 | { 59 | "id": 2, 60 | "text": "In ut nulla posuere, euismod sapien at", 61 | "class": "" 62 | } 63 | ], 64 | "style": { 65 | "fontSize": "16", 66 | "fontWeight": "600", 67 | "textAlign": "left", 68 | "borderRadius": "0", 69 | "color": "#333", 70 | "background": "transparent", 71 | "padding": "10", 72 | "margin": "2", 73 | "listStyle": "none", 74 | "horizontal": "center" 75 | } 76 | } 77 | }, 78 | { 79 | "id": 4, 80 | "name": "Button", 81 | "widget": "ButtonWidget", 82 | "new": true, 83 | "icon": "button-icon.svg", 84 | "content": { 85 | "text": "Button", 86 | "link": "https://github.com/eevan7a9/", 87 | "style": { 88 | "fontSize": "16", 89 | "fontWeight": "600", 90 | "borderRadius": "5", 91 | "color": "#333", 92 | "background": "#007bff", 93 | "padding": "10", 94 | "margin": "2", 95 | "horizontal": "center" 96 | } 97 | } 98 | }, 99 | { 100 | "id": 5, 101 | "name": "Image", 102 | "widget": "ImageWidget", 103 | "new": true, 104 | "icon": "image-icon.svg", 105 | "content": { 106 | "src": "https://i.picsum.photos/id/866/400/300.jpg", 107 | "style": { 108 | "width": "400px", 109 | "borderRadius": "0", 110 | "padding": "2", 111 | "margin": "2", 112 | "horizontal": "center" 113 | } 114 | } 115 | }, 116 | { 117 | "id": 6, 118 | "name": "Spacer", 119 | "widget": "SpacerWidget", 120 | "new": true, 121 | "icon": "spacer-icon.svg", 122 | "content": { 123 | "style": { 124 | "height": "100px", 125 | "background": "#F3F3EE" 126 | } 127 | } 128 | } 129 | ], 130 | "general": [ 131 | { 132 | "id": 1, 133 | "name": "Carousel", 134 | "widget": "CarouselWidget", 135 | "new": true, 136 | "icon": "carousel-icon.svg", 137 | "content": {} 138 | }, 139 | { 140 | "id": 2, 141 | "name": "Social Icon", 142 | "widget": "SocialIconWidget", 143 | "new": true, 144 | "icon": "social-icon.svg", 145 | "content": {} 146 | }, 147 | { 148 | "id": 3, 149 | "name": "Progress Bar", 150 | "widget": "ProgressBarWidget", 151 | "new": true, 152 | "icon": "progressbar-icon.svg", 153 | "content": {} 154 | }, 155 | { 156 | "id": 4, 157 | "name": "Image Gallery", 158 | "widget": "GalleryWidget", 159 | "new": true, 160 | "icon": "gallery-icon.svg", 161 | "content": {} 162 | } 163 | ] 164 | }, 165 | "layouts": [ 166 | { 167 | "label": "6/12 ~ 6/12", 168 | "layout": true, 169 | "columns": [ 170 | { 171 | "id": 1, 172 | "grid": "col-md-6", 173 | "style": { 174 | "fill": true, 175 | "y": "start", 176 | "x": "center", 177 | "background": "none", 178 | "padding": "0" 179 | }, 180 | "elements": [] 181 | }, 182 | { 183 | "id": 2, 184 | "grid": "col-md-6", 185 | "style": { 186 | "fill": true, 187 | "y": "start", 188 | "x": "center", 189 | "background": "none", 190 | "padding": "0" 191 | }, 192 | "elements": [] 193 | } 194 | ] 195 | }, 196 | { 197 | "label": "4/12 ~ 8/12", 198 | "layout": true, 199 | "columns": [ 200 | { 201 | "id": 1, 202 | "grid": "col-md-4", 203 | "style": { 204 | "fill": true, 205 | "y": "start", 206 | "x": "center", 207 | "background": "none", 208 | "padding": "0" 209 | }, 210 | "elements": [] 211 | }, 212 | { 213 | "id": 2, 214 | "grid": "col-md-8", 215 | "style": { 216 | "fill": true, 217 | "y": "start", 218 | "x": "center", 219 | "background": "none", 220 | "padding": "0" 221 | }, 222 | "elements": [] 223 | } 224 | ] 225 | }, 226 | { 227 | "label": "3/12 ~ 9/12", 228 | "layout": true, 229 | "columns": [ 230 | { 231 | "id": 1, 232 | "grid": "col-md-3", 233 | "style": { 234 | "fill": true, 235 | "y": "start", 236 | "x": "center", 237 | "background": "none", 238 | "padding": "0" 239 | }, 240 | "elements": [] 241 | }, 242 | { 243 | "id": 2, 244 | "grid": "col-md-9", 245 | "style": { 246 | "fill": true, 247 | "y": "start", 248 | "x": "center", 249 | "background": "none", 250 | "padding": "0" 251 | }, 252 | "elements": [] 253 | } 254 | ] 255 | }, 256 | { 257 | "label": "3/12(2) ~ 6/12", 258 | "layout": true, 259 | "columns": [ 260 | { 261 | "id": 1, 262 | "grid": "col-md-3", 263 | "style": { 264 | "fill": true, 265 | "y": "start", 266 | "x": "center", 267 | "background": "none", 268 | "padding": "0" 269 | }, 270 | "elements": [] 271 | }, 272 | { 273 | "id": 2, 274 | "grid": "col-md-6", 275 | "style": { 276 | "fill": true, 277 | "y": "start", 278 | "x": "center", 279 | "background": "none", 280 | "padding": "0" 281 | }, 282 | "elements": [] 283 | }, 284 | { 285 | "id": 3, 286 | "grid": "col-md-3", 287 | "style": { 288 | "fill": true, 289 | "y": "start", 290 | "x": "center", 291 | "background": "none", 292 | "padding": "0" 293 | }, 294 | "elements": [] 295 | } 296 | ] 297 | }, 298 | { 299 | "label": "2/12 ~ 4/12 ~ 6/12", 300 | "layout": true, 301 | "columns": [ 302 | { 303 | "id": 1, 304 | "grid": "col-md-2", 305 | "style": { 306 | "fill": true, 307 | "y": "start", 308 | "x": "center", 309 | "background": "none", 310 | "padding": "0" 311 | }, 312 | "elements": [] 313 | }, 314 | { 315 | "id": 2, 316 | "grid": "col-md-4", 317 | "style": { 318 | "fill": true, 319 | "y": "start", 320 | "x": "center", 321 | "background": "none", 322 | "padding": "0" 323 | }, 324 | "elements": [] 325 | }, 326 | { 327 | "id": 3, 328 | "grid": "col-md-6", 329 | "style": { 330 | "fill": true, 331 | "y": "start", 332 | "x": "center", 333 | "background": "none", 334 | "padding": "0" 335 | }, 336 | "elements": [] 337 | } 338 | ] 339 | }, 340 | { 341 | "label": "2/12 ~ 5/12(2)", 342 | "layout": true, 343 | "columns": [ 344 | { 345 | "id": 1, 346 | "grid": "col-md-2", 347 | "style": { 348 | "fill": true, 349 | "y": "start", 350 | "x": "center", 351 | "background": "none", 352 | "padding": "0" 353 | }, 354 | "elements": [] 355 | }, 356 | { 357 | "id": 2, 358 | "grid": "col-md-5", 359 | "style": { 360 | "fill": true, 361 | "y": "start", 362 | "x": "center", 363 | "background": "none", 364 | "padding": "0" 365 | }, 366 | "elements": [] 367 | }, 368 | { 369 | "id": 3, 370 | "grid": "col-md-5", 371 | "style": { 372 | "fill": true, 373 | "y": "start", 374 | "x": "center", 375 | "background": "none", 376 | "padding": "0" 377 | }, 378 | "elements": [] 379 | } 380 | ] 381 | }, 382 | { 383 | "label": "2/12(3) ~ 6/12", 384 | "layout": true, 385 | "columns": [ 386 | { 387 | "id": 1, 388 | "grid": "col-md-2", 389 | "style": { 390 | "fill": true, 391 | "y": "start", 392 | "x": "center", 393 | "background": "none", 394 | "padding": "0" 395 | }, 396 | "elements": [] 397 | }, 398 | { 399 | "id": 2, 400 | "grid": "col-md-2", 401 | "style": { 402 | "fill": true, 403 | "y": "start", 404 | "x": "center", 405 | "background": "none", 406 | "padding": "0" 407 | }, 408 | "elements": [] 409 | }, 410 | { 411 | "id": 3, 412 | "grid": "col-md-2", 413 | "style": { 414 | "fill": true, 415 | "y": "start", 416 | "x": "center", 417 | "background": "none", 418 | "padding": "0" 419 | }, 420 | "elements": [] 421 | }, 422 | { 423 | "id": 4, 424 | "grid": "col-md-6", 425 | "style": { 426 | "fill": true, 427 | "y": "start", 428 | "x": "center", 429 | "background": "none", 430 | "padding": "0" 431 | }, 432 | "elements": [] 433 | } 434 | ] 435 | } 436 | ] 437 | } --------------------------------------------------------------------------------