├── .gitignore ├── .npmignore ├── src ├── consts.js ├── utils │ ├── index.js │ ├── repetition.js │ ├── breadcrumbs.js │ ├── store.js │ ├── palette.js │ └── resizer.js ├── blocks.js ├── panels.js ├── grid.js ├── index.js ├── components.js └── styles.scss ├── TODO.md ├── package.json ├── LICENSE ├── index.html └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | private/ 3 | /locale 4 | node_modules/ 5 | *.log 6 | _index.html 7 | /dist 8 | *.svg -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | private/ 3 | /locale 4 | node_modules/ 5 | *.log 6 | _index.html 7 | TODO.md 8 | *.svg -------------------------------------------------------------------------------- /src/consts.js: -------------------------------------------------------------------------------- 1 | export const 2 | cmdSave = 'save-page', 3 | gridCompId = 'css-grid', 4 | gridChildId = 'grid-cell'; -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - [x] Color palette generator 4 | - [x] DOM breadcrumbs 5 | - [x] Layer icons map 6 | - [x] Grid Component 7 | - [x] Canvas Resizer 8 | - [x] npm package -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import breadcrumbs from './breadcrumbs'; 2 | import palette from './palette'; 3 | import store from './store'; 4 | import resizer from './resizer'; 5 | 6 | export { palette, breadcrumbs, store, resizer }; -------------------------------------------------------------------------------- /src/blocks.js: -------------------------------------------------------------------------------- 1 | import { gridCompId } from './consts' 2 | 3 | export default (editor, opts = {}) => { 4 | const bm = editor.BlockManager; 5 | const { labelGrid, categoryGrid, gridBlock } = opts; 6 | 7 | gridBlock && bm.add(gridCompId, { 8 | label: labelGrid, 9 | media: '', 10 | category: categoryGrid, 11 | content: { type: gridCompId }, 12 | ...gridBlock 13 | }); 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grapesjs-plugin-toolbox", 3 | "version": "1.0.15", 4 | "description": "Grapesjs Plugin Toolbox", 5 | "main": "dist/grapesjs-plugin-toolbox.min.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Ju99ernaut/grapesjs-plugin-toolbox.git" 9 | }, 10 | "scripts": { 11 | "start": "grapesjs-cli serve", 12 | "build": "grapesjs-cli build", 13 | "build:css": "node-sass src/styles.scss dist/grapesjs-plugin-toolbox.min.css --output-style compressed", 14 | "bump": "npm version patch -m 'Bump v%s'" 15 | }, 16 | "keywords": [ 17 | "grapesjs", 18 | "plugin", 19 | "tools" 20 | ], 21 | "devDependencies": { 22 | "grapesjs-cli": "^1.0.12", 23 | "node-sass": "^4.14.1" 24 | }, 25 | "author": "Brendon Ngirazi", 26 | "license": "MIT", 27 | "dependencies": { 28 | "colorthief": "^2.3.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/repetition.js: -------------------------------------------------------------------------------- 1 | export const groupRepeatedUnits = (templateUnitArray = [{ unit: "1fr" }]) => { 2 | const templateArray = templateUnitArray.map(i => i["unit"]); 3 | const groups = [[templateArray.shift()]]; 4 | for (const templateUnit of templateArray) { 5 | const lastGroup = groups[groups.length - 1]; 6 | if (lastGroup.indexOf(templateUnit) !== -1) { 7 | lastGroup.push(templateUnit); 8 | } else { 9 | groups.push([templateUnit]); 10 | } 11 | } 12 | return groups; 13 | }; 14 | 15 | export const createRepetition = (groups, min, auto, maxRepetition = 1) => { 16 | return auto ? `repeat(auto-fill, minmax(${min}px, 1fr))` : groups 17 | .map(group => 18 | // If you want to add repetition only when a measure is repeated more than x times, 19 | // change maxRepetition value to x 20 | group.length === maxRepetition ? group.join(" ") : `repeat(${group.length}, ${group[0]})` 21 | ) 22 | .join(" "); 23 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2020-current Grapesjs Plugin Toolbox 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Grapesjs Plugin Toolbox 7 | 8 | 9 | 10 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | This is a demo content from _index.html. You can use this template file for 24 | development purpose. It won't be stored in your git repository 25 |
26 |
27 | 28 | 29 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/utils/breadcrumbs.js: -------------------------------------------------------------------------------- 1 | export default (editor, config) => { 2 | const generateQuerySelector = el => { 3 | let str = el.tagName.toLowerCase(); 4 | str += el.id ? '#' + el.id : ''; 5 | el.className.length && el.className.split(/\s/).forEach(cls => { 6 | str += (cls != 'gjs-selected' && cls != 'cke_editable' && 7 | cls != 'cke_editable_inline' && cls != 'cke_contents_ltr' && 8 | cls != 'cke_show_borders') ? '.' + cls : ''; 9 | }); 10 | return generateTree(el.parentNode) + `
  • ${str}
  • `; 11 | }; 12 | 13 | const generateTree = el => { 14 | if (el.tagName.toLowerCase() === 'html') 15 | return `
  •        html
  • `; //?link href to components 16 | return generateTree(el.parentNode) + `
  • ${el.tagName.toLowerCase() + (el.id ? '#' + el.id : '')}
  • `; 17 | }; 18 | 19 | const $ = editor.$; 20 | const pfx = editor.Config.stylePrefix; 21 | 22 | editor.on('component:selected', model => { 23 | const breadcrumbs = $(`#${pfx}breadcrumbs`); 24 | !breadcrumbs.length && $('body').append($(`
    `)); 25 | breadcrumbs.html(generateQuerySelector(model.getEl())); 26 | breadcrumbs.find('span').on('click', function(e) { 27 | const doc = editor.Canvas.getDocument(); 28 | editor.select(doc.querySelector(e.currentTarget.innerText.trim())); 29 | }); 30 | }); 31 | } -------------------------------------------------------------------------------- /src/panels.js: -------------------------------------------------------------------------------- 1 | import { cmdSave } from './consts'; 2 | 3 | export default (editor, config) => { 4 | const cm = editor.Commands; 5 | const pn = editor.Panels; 6 | const um = editor.UndoManager; 7 | const pfx = editor.Config.stylePrefix; 8 | 9 | cm.add(cmdSave, e => { 10 | editor.store(res => console.log("Saved...")); 11 | }); 12 | 13 | const pnOptions = pn.getPanel('options'); 14 | pnOptions.get('buttons').add([{ 15 | id: 'undo', 16 | className: 'fa fa-undo', //reply 17 | attributes: { title: 'undo' }, 18 | command: e => e.runCommand('core:undo'), 19 | }, { 20 | id: 'redo', 21 | className: 'fa fa-repeat', //share 22 | attributes: { title: 'redo' }, 23 | command: e => e.runCommand('core:redo'), 24 | }, { 25 | id: cmdSave, 26 | className: 'fa fa-floppy-o', 27 | attributes: { title: 'save' }, 28 | command: e => e.runCommand(cmdSave), 29 | }]); 30 | 31 | const undoBtn = pn.getButton('options', 'undo'); 32 | const redoBtn = pn.getButton('options', 'redo'); 33 | const saveBtn = pn.getButton('options', cmdSave); 34 | 35 | editor.on('component:update', () => { 36 | um.hasUndo() ? undoBtn.set('className', `${pfx}font-aqua fa fa-undo`) : undoBtn.set('className', 'fa fa-undo'); 37 | um.hasRedo() ? redoBtn.set('className', `${pfx}font-aqua fa fa-repeat`) : redoBtn.set('className', 'fa fa-repeat'); 38 | editor.getDirtyCount() > 0 ? saveBtn.set('className', `${pfx}font-green fa fa-floppy-o`) : saveBtn.set('className', 'fa fa-floppy-o'); 39 | }); 40 | } -------------------------------------------------------------------------------- /src/utils/store.js: -------------------------------------------------------------------------------- 1 | import { groupRepeatedUnits, createRepetition } from '../utils/repetition'; 2 | 3 | export default (editor, opts = {}) => { 4 | return { 5 | state: { 6 | auto: false, 7 | min: 200, 8 | columns: 5, 9 | rows: 5, 10 | columngap: 0, 11 | rowgap: 0, 12 | colArr: [], 13 | rowArr: [], 14 | childarea: [] 15 | }, 16 | getters: { 17 | colTemplate(state) { 18 | const unitGroups = groupRepeatedUnits(state.colArr); 19 | return createRepetition(unitGroups, state.min, state.auto); 20 | }, 21 | rowTemplate(state) { 22 | const unitGroups = groupRepeatedUnits(state.rowArr); 23 | return createRepetition(unitGroups, state.min, false); 24 | }, 25 | divNum(state) { 26 | return Math.max(state.columns, 0) * Math.max(state.rows, 0); 27 | } 28 | }, 29 | mutations: { 30 | initialArrIndex(state, payload) { 31 | if (payload !== '') { 32 | const queryParams = new URLSearchParams(payload); 33 | 34 | for (const stateKey in state) { 35 | const paramIsValid = queryParams.has(stateKey); 36 | const paramType = typeof (state[stateKey]); 37 | 38 | if (paramIsValid && paramType === 'number') { 39 | state[stateKey] = queryParams.get(stateKey); 40 | } else if (paramIsValid && paramType === 'object') { 41 | state[stateKey] = JSON.parse(queryParams.get(stateKey)); 42 | } 43 | } 44 | } else { 45 | createArr(state.columns, state.colArr); 46 | createArr(state.rows, state.rowArr); 47 | } 48 | }, 49 | adjustArr(state, payload) { 50 | let newVal = Math.max(Number(payload.newVal), 0), 51 | oldVal = Math.max(Number(payload.oldVal), 0); 52 | 53 | if (newVal < oldVal) { 54 | // you'd think that .length would be quicker here, but it doesn't trigger the getter/computed in colTemplate etc. 55 | let difference = oldVal - newVal; 56 | for (let i = 1; i <= difference; i++) { 57 | state[payload.direction].pop(); 58 | } 59 | } else { 60 | let difference = newVal - oldVal; 61 | for (let i = 1; i <= difference; i++) { 62 | state[payload.direction].push({ unit: "1fr" }); 63 | } 64 | } 65 | }, 66 | addChildren(state, payload) { 67 | state.childarea.push(payload); 68 | }, 69 | removeChildren(state, payload) { 70 | state.childarea.splice(payload, 1); 71 | }, 72 | updateAuto(state, payload) { 73 | state.auto = payload; 74 | }, 75 | updateMin(state, payload) { 76 | state.min = payload; 77 | }, 78 | updateColumns(state, payload) { 79 | state.columns = payload; 80 | }, 81 | updateRows(state, payload) { 82 | state.rows = payload; 83 | }, 84 | updateColumnGap(state, payload) { 85 | state.columngap = payload; 86 | }, 87 | updateRowGap(state, payload) { 88 | state.rowgap = payload; 89 | }, 90 | resetGrid(state, payload) { 91 | state.childarea = []; 92 | } 93 | } 94 | } 95 | }; 96 | 97 | //we start off with just a few rows and columns filled with 1fr units 98 | const createArr = (direction, arr) => { 99 | for (let i = 1; i <= direction; i++) { 100 | arr.push({ unit: "1fr" }); 101 | } 102 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grapesjs Plugin Toolbox 2 | 3 | Tools for `grapesjs` 4 | 5 | [DEMO](https://codepen.io/ju99ernaut/pen/Vwaqdpw) 6 | 7 | ### HTML 8 | ```html 9 | 10 | 11 | 12 | 13 | 14 |
    15 | ``` 16 | 17 | ### JS 18 | ```js 19 | const editor = grapesjs.init({ 20 | container: '#gjs', 21 | height: '100%', 22 | fromElement: true, 23 | storageManager: false, 24 | plugins: ['grapesjs-plugin-toolbox'], 25 | }); 26 | ``` 27 | 28 | ### CSS 29 | ```css 30 | body, html { 31 | margin: 0; 32 | height: 100%; 33 | } 34 | ``` 35 | 36 | 37 | ## Summary 38 | 39 | * Tool in this plugin are 40 | * [Css grid layout tool](https://i.imgur.com/jwXI0MS.mp4) 41 | * [Canvas resizer](https://i.imgur.com/GWBJsmI.mp4) 42 | * [Palette from image generator](https://i.imgur.com/eeIqyHO.png) 43 | * Breadcrumbs 44 | * Layer icon mapper 45 | 46 | * Plugin name: `grapesjs-plugin-toolbox` 47 | * Components 48 | * `css-grid` 49 | * `grid-cell` 50 | * Blocks 51 | * `css-grid` 52 | * Commands 53 | * `add-palette` 54 | 55 | ## Grid Component 56 | 57 | >Here `template guides` referes to the dotted lines and colored template areas whereas `grid cells` referes to the actual divs generated from the `template guide`. 58 | 59 | If the grid component is not empty the update button will only update the css to avoid overwriting any content. Check [here](https://i.imgur.com/jwXI0MS.mp4) to see usage example. 60 | 61 | * Traits 62 | * `auto-fill` - generate grid automatically(disables guides) 63 | * `min` - min cell width in px if auto-fill is used 64 | * `columns` 65 | * `rows` 66 | * `columngap` 67 | * `rowgap` 68 | * `toggle` - show/hide template guides if not auto-fill(same function as toolbar icon) 69 | * `reset` - reset colored template areas used to generate grid 70 | * `clear` - remove grid cells 71 | * `update` - generates grid cells or updates css if grid component isn't empty 72 | 73 | ## Options 74 | 75 | | Option | Description | Default | 76 | |-|-|- 77 | | `panels` | use plugin panels | `false` |' 78 | | `traitsInSm` | move traits/settings to styles panel | `true` | 79 | | `resizer` | include canvas resizer | `true` | 80 | | `hideOnZoom` | hide resizer if zoom is not 100 | `true` | 81 | | `breadcrumbs` | include breadcrumbs | `true` | 82 | | `labelGrid` | label for grid block | `Grid` | 83 | | `categoryGrid` | category for grid block | `Basic` | 84 | | `gridBlock` | options to extend grid block | `{}` | 85 | | `gridComponent` | options to extend grid component model | `{}` | 86 | | `gridClass` | class for grid block | `grid` | 87 | | `gridCellClass` | class for grid cell block | `grid-cell` | 88 | | `cellItemClass` | class for cell item block | `cell-item` | 89 | | `labelColors` | label for color palette modal | `Image palette` | 90 | | `labelApply` | label for apply button | `Add` | 91 | | `palleteIcon` | toolbar icon to open palette modal | `` | 92 | | `onAdd` | custom logic when palette is added | `0` | 93 | | `refreshPalette` | color pickers to refresh color palettes | `[{sector: 'typography',name: 'Color',property: 'color',type: 'color',defaults: 'black'},...]` | 94 | | `minScreenSize` | minimum value the screen can be resized | `250` | 95 | | `icons` | icons to map to components | `[{type: 'default', icon:''},...]` | 96 | 97 | 98 | ## Download 99 | 100 | * CDN 101 | * `https://unpkg.com/grapesjs-plugin-toolbox` 102 | * NPM 103 | * `npm i grapesjs-plugin-toolbox` 104 | * GIT 105 | * `git clone https://github.com/Ju99ernaut/grapesjs-plugin-toolbox.git` 106 | 107 | 108 | 109 | ## Usage 110 | 111 | Directly in the browser 112 | ```html 113 | 114 | 115 | 116 | 117 | 118 |
    119 | 120 | 130 | ``` 131 | 132 | Modern javascript 133 | ```js 134 | import grapesjs from 'grapesjs'; 135 | import plugin from 'grapesjs-plugin-toolbox'; 136 | import 'grapesjs/dist/css/grapes.min.css'; 137 | import 'grapesjs-plugin-toolbox/dist/grapesjs-plugin-toolbox.min.css'; 138 | 139 | const editor = grapesjs.init({ 140 | container : '#gjs', 141 | // ... 142 | plugins: [plugin], 143 | pluginsOpts: { 144 | [plugin]: { /* options */ } 145 | } 146 | // or 147 | plugins: [ 148 | editor => plugin(editor, { /* options */ }), 149 | ], 150 | }); 151 | ``` 152 | 153 | 154 | 155 | ## Development 156 | 157 | Clone the repository 158 | 159 | ```sh 160 | $ git clone https://github.com/Ju99ernaut/grapesjs-plugin-toolbox.git 161 | $ cd grapesjs-plugin-toolbox 162 | ``` 163 | 164 | Install dependencies 165 | 166 | ```sh 167 | $ npm i 168 | ``` 169 | 170 | Build css 171 | 172 | ```sh 173 | $ npm run build:css 174 | ``` 175 | 176 | Start the dev server 177 | 178 | ```sh 179 | $ npm start 180 | ``` 181 | 182 | Build the source 183 | 184 | ```sh 185 | $ npm run build 186 | ``` 187 | 188 | 189 | 190 | ## License 191 | 192 | MIT 193 | -------------------------------------------------------------------------------- /src/utils/palette.js: -------------------------------------------------------------------------------- 1 | import ColorThief from '../../node_modules/colorthief/dist/color-thief.mjs'; 2 | 3 | export default (editor, opts = {}) => { 4 | const colorthief = new ColorThief(); 5 | const { colorsNum, commandId, labelColors, labelApply, paletteIcon, onAdd } = opts; 6 | const $ = editor.$; 7 | const pfx = editor.Config.stylePrefix; 8 | 9 | // Update image component toolbar 10 | const domc = editor.DomComponents; 11 | const typeImage = domc.getType('image').model; 12 | domc.addType('image', { 13 | model: { 14 | initToolbar() { 15 | typeImage.prototype.initToolbar.apply(this, arguments); 16 | const tb = this.get('toolbar'); 17 | const tbExists = tb.some(item => item.command === commandId); 18 | 19 | if (!tbExists) { 20 | tb.unshift({ command: commandId, label: paletteIcon }); 21 | this.set('toolbar', tb); 22 | } 23 | } 24 | } 25 | }); 26 | 27 | // Add the palette add command 28 | editor.Commands.add(commandId, { 29 | run(ed, s, options = {}) { 30 | const { id } = this; 31 | 32 | this.editor = ed; 33 | this.target = options.target || ed.getSelected(); 34 | const colorArr = this.getDominantColor(this.target.getEl()); 35 | this.color = `rgb(${colorArr[0]},${colorArr[1]},${colorArr[2]})`; 36 | const paletteArr = this.getPaletteArray(this.target.getEl()); 37 | this.palette = this.generateColorsFromArray(paletteArr); 38 | const content = this.createContent(this.color, this.palette); 39 | const title = labelColors; 40 | const btn = content.children[1]; 41 | ed.Modal.open({ title, content }) 42 | .getModel().once('change:open', () => ed.stopCommand(id)); 43 | btn.onclick = () => this.addPalette(); 44 | opts.addPalette(btn); 45 | }, 46 | 47 | stop(ed) { 48 | //clear modal 49 | ed.Modal.close(); 50 | }, 51 | 52 | createContent(color, palette) { 53 | const content = $('
    '); 54 | const colorOutput = ` 55 |
    56 |
    57 |
    58 |

    Dominant

    59 |
    60 |
    61 |
    62 |
    63 |
    64 |

    Palette

    65 |
    66 |
    67 | ${palette.map(col => `
    `).join('')} 68 |
    69 |
    70 |
    71 |
    72 |
    ` 73 | content.html(` 74 |
    ${colorOutput}
    75 | 102 | `).join("")} 103 | `); 104 | const del = gridchild.find('button'); 105 | del.on('click', e => this.removeChild(e)); 106 | 107 | return gridchild; 108 | }, 109 | render(cont, store) { 110 | if (!this.container?.length) { 111 | this.el = this.gridEl(store); 112 | this.container = $(cont); 113 | this.container.append(this.el); 114 | editor.on('styleManager:change:height run:smoothresize run:resize', () => { 115 | const st = editor.getSelected()?.get('store'); 116 | st && editor.Grid.visible && editor.Grid.update(st); 117 | }); 118 | } 119 | this.rendered = true; 120 | }, 121 | getEl() { 122 | return this.el.get(0); 123 | }, 124 | select(selected) { 125 | this.selected = selected; 126 | }, 127 | update(store) { 128 | this.el = this.gridEl(store); 129 | $(`#${pfx}grid-main`).replaceWith(this.el); 130 | }, 131 | updateRows(store) { 132 | $(`.${pfx}rowunits`).replaceWith(this.gridRowUnits(store)); 133 | }, 134 | updateCols(store) { 135 | const { width, height, top } = this.dimensions(); 136 | $(`.${pfx}colunits`).replaceWith(this.gridRowUnits(store, width, height, top)); 137 | }, 138 | updateCanvas(store) { 139 | $(`.${pfx}gridcanvas`).replaceWith(this.gridCanvas(store)); 140 | }, 141 | updateChildren(store) { 142 | $(`.${pfx}gridchild`).replaceWith(this.gridCanvasChildren(store)); 143 | }, 144 | child: {}, 145 | widthfull: `${pfx}widthfull`, 146 | widthhalf: `${pfx}widthhalf`, 147 | errors: { col: [], row: [] }, 148 | visible: false, 149 | rendered: false, 150 | dimensions() { 151 | return (this.selected && editor.Canvas.getElementPos(this.selected.getEl())) || { 152 | width: 100, 153 | height: 100 154 | } 155 | }, 156 | validateunit(e) { 157 | const unit = e.target.value; 158 | const i = e.target.getAttribute('data-key'); 159 | const direction = e.target.getAttribute('data-direction'); 160 | const check = 161 | /fr$/.test(unit) || 162 | /px$/.test(unit) || 163 | /%$/.test(unit) || 164 | /em$/.test(unit) || 165 | /rem$/.test(unit) || 166 | /vw$/.test(unit) || 167 | /vh$/.test(unit) || 168 | /vmin$/.test(unit) || 169 | /q$/.test(unit) || 170 | /mm$/.test(unit) || 171 | /cm$/.test(unit) || 172 | /in$/.test(unit) || 173 | /pt$/.test(unit) || 174 | /pc$/.test(unit) || 175 | /ex$/.test(unit) || 176 | /ch$/.test(unit) || 177 | /minmax/.test(unit) || ["auto", "min-content", "max-content"].includes(unit) || 178 | parseInt(unit, 10) === 0; // allow 0 as a valid value without a unit 179 | if (!check) { 180 | this.errors[direction].push(i); 181 | } else { 182 | const store = this.selected.get('store'); 183 | this.errors[direction].splice(this.errors[direction].indexOf(i), 1); 184 | if (direction === 'col') { 185 | store.state.colArr[i].unit = unit; 186 | this.selected.addStyle({ 187 | 'grid-template-columns': store.getters.colTemplate(store.state) 188 | }); 189 | } else { 190 | store.state.rowArr[i].unit = unit; 191 | this.selected.addStyle({ 192 | 'grid-template-rows': store.getters.rowTemplate(store.state) 193 | }); 194 | } 195 | this.update(store); 196 | } 197 | }, 198 | delegatedTouchPlaceChild(ev) { 199 | const target = document.elementFromPoint( 200 | ev.changedTouches[0].clientX, 201 | ev.changedTouches[0].clientY 202 | ); 203 | const startend = ev.type === "touchstart" ? "s" : "e"; 204 | this.placeChild(target.dataset.id, startend); 205 | }, 206 | placeChild(ev, startend) { 207 | const store = this.selected.get('store'); 208 | const item = parseInt(ev.target.getAttribute('data-key')) + 1; 209 | //built an object first because I might use this for something else 210 | this.child[`${startend}row`] = Math.ceil(item / store.state.columns); 211 | this.child[`${startend}col`] = 212 | item - (this.child[`${startend}row`] - 1) * store.state.columns; 213 | //create the children css units as a string 214 | if (startend === "e") { 215 | // flip starts and ends if dragged in the opposite direction 216 | let [startRow, endRow] = 217 | this.child.srow <= this.child.erow ? [this.child.srow, this.child.erow] : [this.child.erow, this.child.srow]; 218 | let [startCol, endCol] = 219 | this.child.scol <= this.child.ecol ? [this.child.scol, this.child.ecol] : [this.child.ecol, this.child.scol]; 220 | let childstring = `${startRow} / ${startCol} / ${endRow + 221 | 1} / ${endCol + 1}`; 222 | store.mutations.addChildren(store.state, childstring); 223 | } 224 | this.updateChildren(store); 225 | }, 226 | removeChild(ev) { 227 | const store = this.selected.get('store'); 228 | const index = ev.target.getAttribute('data-key'); 229 | store.mutations.removeChildren(store.state, index); 230 | this.updateChildren(store); 231 | } 232 | } 233 | }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import loadComponents from './components'; 2 | import loadBlocks from './blocks'; 3 | import loadPanels from './panels'; 4 | import grid from './grid'; 5 | import { breadcrumbs, palette, resizer } from './utils'; 6 | 7 | export default (editor, opts = {}) => { 8 | const options = { 9 | ...{ 10 | // use plugin panels 11 | panels: 0, 12 | // show canvas resizer 13 | resizer: 1, 14 | // show breadcrumbs 15 | breadcrumbs: 1, 16 | // label for grid block 17 | labelGrid: 'Grid', 18 | // category for grid block 19 | categoryGrid: 'Basic', 20 | // options to extend grid block 21 | gridBlock: {}, 22 | // options to extend grid component model 23 | gridComponent: {}, 24 | // options to extend cell component model 25 | cellComponent: {}, 26 | // grid class name 27 | gridClass: 'grid', 28 | // grid cell class name 29 | gridCellClass: 'grid-cell', 30 | // cell item class name 31 | cellItemClass: 'cell-item', 32 | // add palette 33 | palette: 1, 34 | // add palette command 35 | commandId: 'add-palette', 36 | // add palette modal label 37 | labelColors: 'Image palette', 38 | // add palette button text 39 | labelApply: 'Add', 40 | // add palette toolbar icon 41 | paletteIcon: '', 42 | // custom onAdd 43 | // onAdd: (color, palette) => { 44 | // Custom stuff 45 | // } 46 | onAdd: 0, 47 | // The add button (HTMLElement) will be passed as an argument to this function, once created. 48 | // This will allow you a higher customization. 49 | addPalette: () => { }, 50 | // Number of colors to generate for pallete 51 | colorsNum: 9, 52 | // Refresh inputs on palette update 53 | refreshPalette: [{ 54 | sector: 'typography', 55 | name: 'Color', 56 | property: 'color', 57 | type: 'color', 58 | defaults: 'black' 59 | }, { 60 | sector: 'decorations', 61 | name: 'Background color', 62 | property: 'background-color', 63 | type: 'color' 64 | }], 65 | // Minimum value the screen can be resized 66 | minScreenSize: 250, 67 | // Dampen the drag speed 68 | dragDampen: 1, 69 | // Hide if zoom is not 100 70 | hideOnZoom: 1, 71 | // Add settings to style manager 72 | traitsInSm: 1, 73 | // Wrapper icon 74 | wrapperIcon: 0, 75 | // Icons Map 76 | icons: [], 77 | defIcons: [{ 78 | type: 'body', 79 | icon: '' 80 | }, 81 | { 82 | type: 'wrapper', 83 | icon: '' 84 | }, 85 | { 86 | type: 'custom-code', 87 | icon: '' 88 | }, 89 | { 90 | type: 'script', 91 | icon: '' 92 | }, 93 | { 94 | type: 'comment', 95 | icon: '' 96 | }, 97 | { 98 | type: 'text', 99 | icon: '' 100 | }, 101 | { 102 | type: 'textnode', 103 | icon: '' 104 | }, 105 | { 106 | type: 'header', 107 | icon: '' 108 | }, 109 | { 110 | type: 'box', 111 | icon: '' 112 | }, 113 | { 114 | type: 'section', 115 | icon: '' 116 | }, 117 | { 118 | type: 'link', 119 | icon: '' 120 | }, 121 | { 122 | type: 'footer', 123 | icon: '' 124 | }, 125 | { 126 | type: 'input', 127 | icon: '' 128 | }, 129 | { 130 | type: 'button', 131 | icon: '' 132 | }, 133 | { 134 | type: 'image', 135 | icon: '' 136 | }, 137 | { 138 | type: 'video', 139 | icon: '' 140 | }, 141 | { 142 | type: 'row', 143 | icon: '' 144 | }, 145 | { 146 | type: 'cell', 147 | icon: '' 148 | }, 149 | { 150 | type: 'table', 151 | icon: '' 152 | }, 153 | { 154 | type: 'thead', 155 | icon: '' 156 | }, 157 | { 158 | type: 'tbody', 159 | icon: '' 160 | }, 161 | { 162 | type: 'tfoot', 163 | icon: '' 164 | }, 165 | { 166 | type: 'column', 167 | icon: '' 168 | }, 169 | { 170 | type: 'map', 171 | icon: '' 172 | }, 173 | { 174 | type: 'label', 175 | icon: '' 176 | }, 177 | { 178 | type: 'checkbox', 179 | icon: '' 180 | }, 181 | { 182 | type: 'textarea', 183 | icon: '' 184 | }, 185 | { 186 | type: 'select', 187 | icon: '' 188 | }, 189 | { 190 | type: 'radio', 191 | icon: '' 192 | }, 193 | { 194 | type: 'form', 195 | icon: '' 196 | }, 197 | { 198 | type: 'svg', 199 | icon: '' 200 | }, 201 | { 202 | type: 'svg-in', 203 | icon: '' 204 | }, 205 | { 206 | type: 'nav', 207 | icon: '' 208 | }, 209 | { 210 | type: 'navbar', 211 | icon: '' 212 | }, 213 | { 214 | type: 'navbar-container', 215 | icon: '' 216 | }, 217 | { 218 | type: 'navbar-menu', 219 | icon: '' 220 | }, 221 | { 222 | type: 'burger-menu', 223 | icon: '' 224 | }, 225 | { 226 | type: 'burger-line', 227 | icon: '' 228 | }, 229 | { 230 | type: 'span', 231 | icon: '' 232 | }, 233 | { 234 | type: 'countdown', 235 | icon: '' 236 | }, 237 | { 238 | type: 'twitch', 239 | icon: '' 240 | }, 241 | { 242 | type: 'tooltip', 243 | icon: '' 244 | }, 245 | { 246 | type: 'tabs', 247 | icon: '' 248 | }, 249 | { 250 | type: 'tab', 251 | icon: '' 252 | }, 253 | { 254 | type: 'tab-container', 255 | icon: '' 256 | }, 257 | { 258 | type: 'tab-content', 259 | icon: '' 260 | }, 261 | { 262 | type: 'lory-slider', 263 | icon: '' 264 | }, 265 | { 266 | type: 'lory-frame', 267 | icon: '' 268 | }, 269 | { 270 | type: 'lory-slides', 271 | icon: '' 272 | }, 273 | { 274 | type: 'lory-slide', 275 | icon: '' 276 | }, 277 | { 278 | type: 'lory-prev', 279 | icon: '' 280 | }, 281 | { 282 | type: 'lory-next', 283 | icon: '' 284 | }, 285 | { 286 | type: 'typed', 287 | icon: '' 288 | }, 289 | { 290 | type: 'default', 291 | icon: '' 292 | } 293 | ] 294 | }, 295 | ...opts 296 | }; 297 | 298 | // Load grid 299 | editor.Grid = grid(editor, options); 300 | // Add components 301 | loadComponents(editor, options); 302 | // Add blocks 303 | loadBlocks(editor, options); 304 | // Add panels 305 | options.panels && loadPanels(editor, options); 306 | // Add breadcrumbs 307 | options.breadcrumbs && breadcrumbs(editor, options); 308 | // Add palette 309 | options.palette && palette(editor, options); 310 | // Load resizer 311 | options.resizer && resizer(editor, options); 312 | 313 | editor.on('load', () => { 314 | const $ = editor.$; 315 | const pn = editor.Panels; 316 | const pfx = editor.Config.stylePrefix; 317 | const cmp = editor.Components; 318 | 319 | //? Map layer icons to components 320 | options.defIcons.forEach(icon => { 321 | try { 322 | cmp.getType(icon.type).model.prototype.defaults.icon = icon.icon; 323 | } catch (error) { } 324 | }) 325 | options.icons.forEach(icon => { 326 | try { 327 | cmp.getType(icon.type).model.prototype.defaults.icon = icon.icon; 328 | } catch (error) { } 329 | }); 330 | 331 | // Add Settings Sector 332 | if (options.traitsInSm) { 333 | // Load and show settings and style manager 334 | const openTmBtn = pn.getButton('views', 'open-tm'); 335 | openTmBtn && openTmBtn.set('active', 1); 336 | const openSm = pn.getButton('views', 'open-sm'); 337 | openSm && openSm.set('active', 1); 338 | 339 | const traitsSector = $(`
    Settings
    `); 340 | const traitsProps = traitsSector.find(`.${pfx}sm-properties`); 341 | traitsProps.append($(`.${pfx}trt-traits`)); 342 | $(`.${pfx}sm-sectors`).before(traitsSector); 343 | traitsSector.find(`.${pfx}sm-title`).on('click', function () { 344 | let traitStyle = traitsProps.get(0).style; 345 | let hidden = traitStyle.display == 'none'; 346 | if (hidden) { 347 | traitStyle.display = 'block'; 348 | } else { 349 | traitStyle.display = 'none'; 350 | } 351 | }); 352 | pn.removeButton('views', 'open-tm'); 353 | $(`.${pfx}pn-views .${pfx}pn-btn`).css('width', `${100 / pn.getPanel('views').buttons.length}%`); 354 | } 355 | 356 | // Body icon 357 | if (options.wrapperIcon) { 358 | const openLm = pn.getButton('views', 'open-layers'); 359 | openLm && openLm.set('active', 1); 360 | $(`.${pfx}layer-name`)[0].innerHTML = ' Body'; 361 | openSm && openSm.set('active', 1); 362 | } 363 | }); 364 | }; -------------------------------------------------------------------------------- /src/components.js: -------------------------------------------------------------------------------- 1 | import { gridCompId, gridChildId } from './consts'; 2 | import { store } from './utils'; 3 | 4 | export default (editor, opts = {}) => { 5 | const domc = editor.DomComponents; 6 | const pfx = editor.Config.stylePrefix; 7 | const { 8 | gridComponent, 9 | cellComponent, 10 | gridClass, 11 | gridCellClass, 12 | cellItemClass 13 | } = opts; 14 | 15 | const idTrait = { name: 'id', label: 'Id' }; 16 | 17 | const titleTrait = { name: 'title', label: 'Title' }; 18 | 19 | const privateCls = [`.${gridClass}`, `.${cellItemClass}`, `.${gridCellClass}`]; 20 | editor.on( 21 | 'selector:add', 22 | selector => 23 | privateCls.indexOf(selector.getFullName()) >= 0 && 24 | selector.set('private', 1) 25 | ); 26 | 27 | const showGrid = (ed) => { 28 | if (!ed.getSelected().get('auto')) { 29 | const st = ed.Grid.selected.get('store'); 30 | if (!editor.Grid.container?.length) editor.Grid.render(`#${pfx}tools`, st); 31 | ed.Grid.visible = !ed.Grid.visible; 32 | ed.Grid.update(st); 33 | } 34 | } 35 | 36 | const addCellTrait = { 37 | name: 'addCell', 38 | type: 'button', 39 | full: true, 40 | text: 'Add Cell', 41 | command: editor => { 42 | const comp = editor.getSelected(); 43 | comp && comp.components().add(`
    `); 58 | const cc = editor.Css; 59 | cc.getRule(`.${cellItemClass}`) || cc.setRule(`.${cellItemClass}`, { 60 | 'min-height': '75px', 61 | 'flex-grow': 1, 62 | 'flex-basis': '100%' 63 | }); 64 | } 65 | } 66 | 67 | const autoTrait = { 68 | name: 'auto', 69 | label: 'Auto Fill', 70 | type: 'checkbox', 71 | changeProp: 1, 72 | } 73 | 74 | const columnsTrait = { 75 | name: 'columns', 76 | label: 'Columns', 77 | type: 'number', 78 | changeProp: 1, 79 | placeholder: '6', 80 | min: 1, 81 | } 82 | 83 | const rowsTrait = { 84 | name: 'rows', 85 | label: 'Rows', 86 | type: 'number', 87 | changeProp: 1, 88 | placeholder: '6', 89 | min: 1, 90 | } 91 | 92 | const columnGapTrait = { 93 | name: 'columngap', 94 | label: 'Column Gap(px)', 95 | type: 'number', 96 | changeProp: 1, 97 | placeholder: '0', 98 | min: 0, 99 | } 100 | 101 | const rowGapTrait = { 102 | name: 'rowgap', 103 | label: 'Row Gap(px)', 104 | type: 'number', 105 | changeProp: 1, 106 | placeholder: '0', 107 | min: 0, 108 | } 109 | 110 | const minTrait = { 111 | name: 'min', 112 | label: 'Min(px)', 113 | type: 'number', 114 | changeProp: 1, 115 | placeholder: '0', 116 | min: 1, 117 | } 118 | 119 | const toggleTrait = { 120 | name: 'toggle', 121 | label: 'Guides', 122 | type: 'button', 123 | full: true, 124 | text: 'Toggle', 125 | command: e => showGrid(e) 126 | } 127 | 128 | const clearTrait = { 129 | name: 'clear', 130 | label: 'Cells', 131 | type: 'button', 132 | full: true, 133 | text: 'Clear', 134 | command: editor => editor.getSelected().components.reset() 135 | } 136 | 137 | const resetTrait = { 138 | name: 'reset', 139 | label: 'Guides', 140 | type: 'button', 141 | full: true, 142 | text: 'Reset', 143 | command: editor => { 144 | const store = editor.Grid.selected.get('store'); 145 | store.mutations.resetGrid(store.state); 146 | editor.Grid.updateChildren(store); 147 | } 148 | } 149 | 150 | const updateTrait = { 151 | name: 'update', 152 | label: 'Cells', 153 | type: 'button', 154 | full: true, 155 | text: 'Update', 156 | command: editor => { 157 | //Generate html and css 158 | const sel = editor.getSelected(); 159 | const { state, getters } = sel.get('store'); 160 | let grid = state.auto ? Array(state.rows * state.columns).fill().map(i => { 161 | return `
    ` 162 | }).join("") : 163 | state.childarea.map((area, i) => { 164 | return `
    ` 165 | }).join(""); 166 | const css = state.childarea.map((area, i) => { 167 | return `.${sel.getId() + '-div' + i}{grid-area:${area}}` 168 | }).join(""); 169 | !sel.components().length > 0 && sel.components().reset(grid); 170 | editor.addStyle(generateMedia(css)); 171 | (editor.Grid.visible = false) || (editor.Grid.getEl().style.display = 'none'); 172 | sel.addStyle({ 173 | 'grid-template-columns': getters.colTemplate(state) 174 | }); 175 | } 176 | } 177 | 178 | const generateMedia = (css) => { 179 | const deviceId = editor.getDevice(); 180 | const device = editor.Devices.get(deviceId); 181 | if (device.get('widthMedia')) { 182 | return `@media (${editor.Config.mediaCondition}: ${device.get('widthMedia')}){ 183 | ${css} 184 | }` 185 | } 186 | return css; 187 | } 188 | 189 | const toConfigState = (state) => { 190 | return state && Object.keys(state).length ? { state } : undefined; 191 | } 192 | 193 | domc.addType(gridChildId, { 194 | model: { 195 | defaults: { 196 | icon: '', 197 | traits: [idTrait, titleTrait, addCellTrait], 198 | }, 199 | init() { 200 | const cc = editor.Css; 201 | this.get('classes').pluck('name').indexOf(gridCellClass) < 0 && this.addClass(gridCellClass); 202 | cc.getRule(`.${gridCellClass}`) || cc.setRule(`.${gridCellClass}`, { 203 | display: 'flex', 204 | 'justify-content': 'flex-start', 205 | 'align-items': 'stretch', 206 | padding: '5px' 207 | }); 208 | }, 209 | ...cellComponent 210 | } 211 | }); 212 | 213 | const toolbar = [{ 214 | attributes: { class: 'fa fa-table' }, 215 | command: e => showGrid(e) 216 | }, { 217 | attributes: { class: 'fa fa-arrow-up' }, 218 | command: e => e.runCommand('core:component-exit', { force: 1 }) 219 | }, { 220 | attributes: { 221 | class: 'fa fa-arrows gjs-no-touch-actions', 222 | draggable: true 223 | }, 224 | command: 'tlb-move' 225 | }, { 226 | attributes: { class: 'fa fa-clone' }, 227 | command: 'tlb-clone' 228 | }, { 229 | attributes: { class: 'fa fa-trash-o' }, 230 | command: 'tlb-delete' 231 | }] 232 | 233 | domc.addType(gridCompId, { 234 | model: { 235 | defaults: { 236 | // Default props 237 | icon: '', 238 | toolbar, 239 | traits: [ 240 | idTrait, 241 | titleTrait, 242 | autoTrait, 243 | minTrait, 244 | columnsTrait, 245 | rowsTrait, 246 | columnGapTrait, 247 | rowGapTrait, 248 | toggleTrait, 249 | resetTrait, 250 | clearTrait, 251 | updateTrait 252 | ], 253 | resizable: { 254 | tl: 0, 255 | tc: 0, 256 | tr: 0, 257 | cl: 0, 258 | cr: 0, 259 | bl: 0, 260 | br: 0, 261 | minDim: 5 262 | }, 263 | min: 200, 264 | auto: false 265 | }, 266 | init() { 267 | const cc = editor.Css; 268 | const storedState = this.get('store')?.state; 269 | const st = { 270 | ...store(opts), 271 | ...toConfigState(storedState || {}) 272 | }; 273 | !storedState && st.mutations.initialArrIndex(st.state, ''); 274 | this.set('rows', st.state.rows); 275 | this.set('columns', st.state.columns); 276 | this.set('rowgap', st.state.rowgap); 277 | this.set('columngap', st.state.columngap); 278 | this.set('store', st); 279 | this.addStyle({ 280 | 'grid-template-rows': st.getters.rowTemplate(st.state), 281 | 'grid-template-columns': st.getters.colTemplate(st.state), 282 | }); 283 | this.get('classes').pluck('name').indexOf(gridClass) < 0 && this.addClass(gridClass); 284 | cc.getRule(`.${gridClass}`) || cc.setRule(`.${gridClass}`, { 285 | display: 'grid', 286 | padding: '10px', 287 | height: '95%', 288 | width: '100%', 289 | }); 290 | this.on("change:auto", this.updateAuto); 291 | this.on("change:rows", this.updateRows); 292 | this.on("change:columns", this.updateColumns); 293 | this.on("change:rowgap", this.updateRowgap); 294 | this.on("change:columngap", this.updateColumngap); 295 | this.on("change:min", this.updateMin); 296 | this.on("change:status", this.onStatusChange); 297 | }, 298 | updateRows() { 299 | const rows = parseInt(this.get('rows')); 300 | const store = this.get('store'); 301 | const payload = { 302 | newVal: rows, 303 | oldVal: store.state.rows, 304 | direction: "rowArr" 305 | }; 306 | store.mutations.updateRows(store.state, rows); 307 | store.mutations.adjustArr(store.state, payload); 308 | editor.Grid.update(store); 309 | this.addStyle({ 310 | 'grid-template-rows': store.getters.rowTemplate(store.state) 311 | }); 312 | }, 313 | updateColumns() { 314 | const columns = parseInt(this.get('columns')); 315 | const store = this.get('store'); 316 | const payload = { 317 | newVal: columns, 318 | oldVal: store.state.columns, 319 | direction: "colArr" 320 | }; 321 | store.mutations.updateColumns(store.state, columns); 322 | store.mutations.adjustArr(store.state, payload); 323 | editor.Grid.update(store); 324 | this.addStyle({ 325 | 'grid-template-columns': store.getters.colTemplate(store.state) 326 | }); 327 | }, 328 | updateRowgap() { 329 | const rowgap = this.get('rowgap'); 330 | const store = this.get('store'); 331 | store.mutations.updateRowGap(store.state, parseInt(rowgap)); 332 | editor.Grid.updateChildren(store); 333 | this.addStyle({ 'grid-row-gap': `${rowgap}px` }); 334 | }, 335 | updateColumngap() { 336 | const columngap = this.get('columngap'); 337 | const store = this.get('store'); 338 | store.mutations.updateColumnGap(store.state, parseInt(columngap)); 339 | editor.Grid.updateChildren(store); 340 | this.addStyle({ 'grid-column-gap': `${columngap}px` }); 341 | }, 342 | updateMin() { 343 | const min = parseInt(this.get('min')); 344 | const store = this.get('store'); 345 | store.mutations.updateMin(store.state, min); 346 | }, 347 | updateAuto() { 348 | const auto = !!this.get('auto'); 349 | const store = this.get('store'); 350 | store.mutations.updateAuto(store.state, auto); 351 | if (auto) (editor.Grid.visible = false) || (editor.Grid.getEl().style.display = 'none'); 352 | }, 353 | onStatusChange() { 354 | const status = this.get('status'); 355 | if (!editor.Grid.container?.length) editor.Grid.render(`#${pfx}tools`, this.get('store')); 356 | if (status === 'selected') editor.Grid.select(editor.getSelected()); 357 | else (editor.Grid.visible = false) || (editor.Grid.getEl().style.display = 'none'); 358 | }, 359 | ...gridComponent 360 | } 361 | }); 362 | }; 363 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* Class name prefixes */ 2 | $prefix: 'gjs-' !default; 3 | 4 | /* Colors / Theme */ 5 | $breadcrumbsColor1: #3b75f3; 6 | $breadcrumbsColor2: #376de2; 7 | $breadcrumbsHoverColor: #6c8fdb; 8 | 9 | .#{$prefix}font-aqua { 10 | color: #b6e2e5; 11 | } 12 | 13 | .#{$prefix}font-blue { 14 | color: #c9a1d1; 15 | } 16 | 17 | .#{$prefix}font-pink { 18 | color: #d1afc7; 19 | } 20 | 21 | .#{$prefix}font-green { 22 | color: #9ee7a8; 23 | } 24 | 25 | .#{$prefix}font-red { 26 | color: #ca6868; 27 | } 28 | 29 | .#{$prefix}font-orange { 30 | color: #cf9269; 31 | } 32 | 33 | .#{$prefix}sm-property__margin .#{$prefix}sm-properties::after, 34 | .#{$prefix}sm-property__padding .#{$prefix}sm-properties::after { 35 | content: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBpZD0iZTkzOWQwODYtNjM4Zi00OTk2LTg3ZmMtYzhmOGUyZTc2ZWQ4IiBkYXRhLW5hbWU9IkxheWVyIDEiIHdpZHRoPSI3MCIgaGVpZ2h0PSI0MCI+PHJlY3Qgd2lkdGg9IjcwIiBoZWlnaHQ9IjQwIiBmaWxsPSJyZ2JhKDAsMCwwLDAuMSkiIHN0cm9rZT0icmdiYSgwLDAsMCwwLjUpIi8+PC9zdmc+"); 36 | position: absolute; 37 | left: 30%; 38 | top: 30%; 39 | } 40 | 41 | .#{$prefix}sm-property__margin-top, 42 | .#{$prefix}sm-property__margin-bottom, 43 | .#{$prefix}sm-property__margin-right, 44 | .#{$prefix}sm-property__margin-left, 45 | .#{$prefix}sm-property__padding-top, 46 | .#{$prefix}sm-property__padding-bottom, 47 | .#{$prefix}sm-property__padding-right, 48 | .#{$prefix}sm-property__padding-left { 49 | flex: 999 1 60px; 50 | } 51 | 52 | .#{$prefix}sm-property__margin-top, 53 | .#{$prefix}sm-property__margin-bottom, 54 | .#{$prefix}sm-property__margin-right, 55 | .#{$prefix}sm-property__margin-left, 56 | .#{$prefix}sm-property__padding-top, 57 | .#{$prefix}sm-property__padding-bottom, 58 | .#{$prefix}sm-property__padding-right, 59 | .#{$prefix}sm-property__padding-left { 60 | max-width: 70px; 61 | flex: 999 1 60px; 62 | } 63 | 64 | .#{$prefix}sm-property__top .#{$prefix}sm-label, 65 | .#{$prefix}sm-property__bottom .#{$prefix}sm-label, 66 | .#{$prefix}sm-property__right .#{$prefix}sm-label, 67 | .#{$prefix}sm-property__left .#{$prefix}sm-label, 68 | .#{$prefix}sm-property__margin-top .#{$prefix}sm-label, 69 | .#{$prefix}sm-property__margin-bottom .#{$prefix}sm-label, 70 | .#{$prefix}sm-property__margin-right .#{$prefix}sm-label, 71 | .#{$prefix}sm-property__margin-left .#{$prefix}sm-label, 72 | .#{$prefix}sm-property__padding-top .#{$prefix}sm-label, 73 | .#{$prefix}sm-property__padding-bottom .#{$prefix}sm-label, 74 | .#{$prefix}sm-property__padding-right .#{$prefix}sm-label, 75 | .#{$prefix}sm-property__padding-left .#{$prefix}sm-label { 76 | display: none; 77 | } 78 | 79 | .#{$prefix}sm-property__margin-top .#{$prefix}field, 80 | .#{$prefix}sm-property__margin-bottom .#{$prefix}field, 81 | .#{$prefix}sm-property__margin-right .#{$prefix}field, 82 | .#{$prefix}sm-property__margin-left .#{$prefix}field, 83 | .#{$prefix}sm-property__padding-top .#{$prefix}field, 84 | .#{$prefix}sm-property__padding-bottom .#{$prefix}field, 85 | .#{$prefix}sm-property__padding-right .#{$prefix}field, 86 | .#{$prefix}sm-property__padding-left .#{$prefix}field { 87 | background-color: transparent; 88 | } 89 | 90 | .#{$prefix}sm-property__margin-left, 91 | .#{$prefix}sm-property__padding-left { 92 | margin-left: -15px; 93 | order: 2; 94 | } 95 | 96 | .#{$prefix}sm-property__margin-right, 97 | .#{$prefix}sm-property__padding-right { 98 | margin-left: 54px; 99 | margin-right: -15px; 100 | order: 3; 101 | } 102 | 103 | .#{$prefix}sm-property__bottom, 104 | .#{$prefix}sm-property__margin-bottom, 105 | .#{$prefix}sm-property__padding-bottom { 106 | order: 4; 107 | margin-left: 25%; 108 | margin-right: 25%; 109 | } 110 | 111 | .#{$prefix}sm-property__top, 112 | .#{$prefix}sm-property__margin-top, 113 | .#{$prefix}sm-property__padding-top { 114 | margin-left: 28.5%; 115 | margin-right: 25%; 116 | } 117 | 118 | .#{$prefix}pn-commands { 119 | min-height: 40px; 120 | } 121 | 122 | .#{$prefix}pn-panel.#{$prefix}pn-views { 123 | padding: 0; 124 | } 125 | 126 | .#{$prefix}pn-views .#{$prefix}pn-btn { 127 | margin: 0; 128 | height: 40px; 129 | padding: 10px; 130 | } 131 | 132 | .CodeMirror { 133 | min-height: 450px; 134 | margin-bottom: 8px; 135 | } 136 | 137 | .grp-handler-close { 138 | background-color: transparent; 139 | color: #ddd; 140 | } 141 | 142 | .grp-handler-cp-wrap { 143 | border-color: transparent; 144 | } 145 | 146 | /*Breadcrumbs*/ 147 | ##{$prefix}breadcrumbs { 148 | position: absolute; 149 | left: 0px; 150 | bottom: 0px; 151 | display: inline-block; 152 | z-index: 1; 153 | margin: 0; 154 | padding: 0; 155 | 156 | .icon { 157 | font-size: 14px; 158 | } 159 | 160 | li { 161 | float: left; 162 | list-style-type: none; 163 | cursor: pointer; 164 | 165 | a { 166 | color: whitesmoke; 167 | display: block; 168 | background-color: $breadcrumbsColor1; 169 | /*$quaternaryColor;*/ 170 | text-decoration: none; 171 | position: relative; 172 | height: 26px; 173 | line-height: 26px; 174 | padding: 0 10px 0 5px; 175 | text-align: center; 176 | margin-right: 23px; 177 | } 178 | 179 | &:nth-child(even) { 180 | a { 181 | background-color: $breadcrumbsColor2; 182 | /*$classColor;*/ 183 | 184 | &:before { 185 | border-color: $breadcrumbsColor2; 186 | /*$classColor;*/ 187 | border-left-color: transparent; 188 | } 189 | 190 | &:after { 191 | border-left-color: $breadcrumbsColor2; 192 | /*$classColor;*/ 193 | } 194 | } 195 | } 196 | 197 | &:first-child { 198 | a { 199 | padding-left: 15px; 200 | /*@include border-radius(4px 0 0 4px);*/ 201 | border-radius: 4px 0 0 4px; 202 | 203 | &:before { 204 | border: none; 205 | } 206 | } 207 | } 208 | 209 | &:last-child { 210 | a { 211 | padding-right: 15px; 212 | /*@include border-radius(0 4px 4px 0);*/ 213 | border-radius: 0 4px 4px 0; 214 | 215 | &:after { 216 | border: none; 217 | } 218 | } 219 | } 220 | 221 | a { 222 | 223 | &:before, 224 | &:after { 225 | content: ""; 226 | position: absolute; 227 | top: 0; 228 | border: 0 solid $breadcrumbsColor1; 229 | /*$quatenaryColor;*/ 230 | border-width: 13px 10px; 231 | width: 0; 232 | height: 0; 233 | } 234 | 235 | &:before { 236 | left: -20px; 237 | border-left-color: transparent; 238 | } 239 | 240 | &:after { 241 | left: 100%; 242 | border-color: transparent; 243 | border-left-color: $breadcrumbsColor1; 244 | /*$quatenaryColor;*/ 245 | } 246 | 247 | &:hover { 248 | background-color: $breadcrumbsHoverColor; 249 | /*$classColor;*/ 250 | 251 | &:before { 252 | border-color: $breadcrumbsHoverColor; 253 | /*$classColor;*/ 254 | border-left-color: transparent; 255 | } 256 | 257 | &:after { 258 | border-left-color: $breadcrumbsHoverColor; 259 | /*$classColor;*/ 260 | } 261 | } 262 | 263 | &:active { 264 | background-color: $breadcrumbsHoverColor; 265 | /*$classColor;*/ 266 | 267 | &:before { 268 | border-color: $breadcrumbsHoverColor; 269 | /*$classColor;*/ 270 | border-left-color: transparent; 271 | } 272 | 273 | &:after { 274 | border-left-color: $breadcrumbsHoverColor; 275 | /*$classColor;*/ 276 | } 277 | } 278 | } 279 | } 280 | } 281 | 282 | /* Color Palette */ 283 | .#{$prefix}color-thief-output { 284 | display: block; 285 | padding: 1.5rem; 286 | background-color: rgba(0, 0, 0, 0.2); 287 | border-top-width: 0; 288 | border-radius: 5px; 289 | } 290 | 291 | .#{$prefix}output-layout { 292 | display: flex; 293 | } 294 | 295 | .#{$prefix}function.#{$prefix}get-color { 296 | flex: 0 1 16rem; 297 | margin-right: 2rem; 298 | } 299 | 300 | .#{$prefix}function-title { 301 | text-align: left; 302 | margin-top: 0; 303 | } 304 | 305 | .#{$prefix}get-color .swatch { 306 | width: 4rem; 307 | height: 4rem; 308 | } 309 | 310 | .#{$prefix}get-palette .swatch { 311 | width: 3rem; 312 | height: 3rem; 313 | } 314 | 315 | .swatch { 316 | display: inline-block; 317 | background: #dddddd; 318 | border-radius: 50%; 319 | } 320 | 321 | ##{$prefix}grid-main { 322 | margin: 3px; 323 | pointer-events: all; 324 | z-index: 1; 325 | } 326 | 327 | @mixin colors($max, $color-frequency) { 328 | $color: 300 / $max; 329 | 330 | // fruit loops! 331 | @for $i from 1 through $max { 332 | div[class*="child"]:nth-child(#{$i}) { 333 | /*background: hsla(($i - 15) * ($color * 1.5), 80%, 30%, 0.5);*/ 334 | background: hsla($color-frequency + ($i - 15) * ($color * 1.5), 80%, 30%, 0.5); 335 | border: 1px solid #ddd; 336 | } 337 | } 338 | 339 | div[class*="child"] { 340 | background: hsla(300, 80%, 30%, 0.7); 341 | border: 1px solid #ddd; 342 | } 343 | } 344 | 345 | .#{$prefix}gridchild { 346 | counter-reset: step; 347 | 348 | div { 349 | counter-increment: step; 350 | position: relative; 351 | 352 | &:before { 353 | position: absolute; 354 | content: ".div"counter(step); 355 | display: block; 356 | padding: 0 5px; 357 | text-align: center; 358 | color: white; 359 | } 360 | 361 | button { 362 | position: absolute; 363 | right: 0; 364 | padding: 0 5px; 365 | margin: 0; 366 | color: white; 367 | background-color: transparent; 368 | border: none; 369 | z-index: 99999; 370 | } 371 | } 372 | } 373 | 374 | ##{$prefix}gridcontainer { 375 | border: 2px solid #71db9a; 376 | width: 100%; 377 | height: 100%; 378 | z-index: 0; 379 | position: relative; 380 | box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.15); 381 | } 382 | 383 | .#{$prefix}grid { 384 | width: 100%; 385 | height: 100%; 386 | position: absolute; 387 | display: grid; 388 | grid-auto-flow: row dense; 389 | @include colors(20, 100); 390 | 391 | p { 392 | padding: 0 10px; 393 | } 394 | 395 | div[class*="box"] { 396 | background-image: url("data:image/svg+xml,%3Csvg width='8' height='8' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%239C92AC' fill-opacity='0.4' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E"); 397 | border: 1px dotted gray; 398 | transition: 0.2s all ease; 399 | cursor: move; 400 | position: relative; 401 | z-index: 1000; 402 | opacity: 0.5; 403 | } 404 | } 405 | 406 | .#{$prefix}rowunits, 407 | .#{$prefix}colunits { 408 | display: grid; 409 | position: absolute; 410 | z-index: 1; 411 | 412 | div { 413 | text-align: center; 414 | position: relative; 415 | } 416 | } 417 | 418 | .#{$prefix}rowunits { 419 | float: left; 420 | height: 100%; 421 | 422 | div { 423 | align-self: center; 424 | } 425 | } 426 | 427 | .#{$prefix}widthfull { 428 | width: 100%; 429 | } 430 | 431 | .#{$prefix}widthhalf { 432 | width: 50%; 433 | } 434 | 435 | .#{$prefix}errors { 436 | position: absolute; 437 | bottom: -5px; 438 | border-radius: 4px; 439 | padding: 8px 12px; 440 | z-index: 1; 441 | font-weight: bold; 442 | width: 150px; 443 | min-height: 50px; 444 | background: #6d1a39; 445 | } 446 | 447 | /* RESEZING GLUTTER */ 448 | .iframe-handle-container .handle { 449 | position: absolute; 450 | top: 0; 451 | bottom: 0; 452 | z-index: 14; 453 | } 454 | 455 | .iframe-handle-container .handle:before { 456 | content: ""; 457 | position: absolute; 458 | top: 0; 459 | left: -3px; 460 | width: 4px; 461 | height: 100%; 462 | background: #178df7; 463 | display: none; 464 | } 465 | 466 | .iframe-handle-container .right-handle { 467 | left: -1px; 468 | width: 14px; 469 | } 470 | 471 | .iframe-handle-container .left-handle { 472 | left: -1px; 473 | width: 3px; 474 | pointer-events: all; 475 | cursor: col-resize; 476 | } 477 | 478 | .iframe-handle-container .tab-handle { 479 | position: absolute; 480 | top: 50%; 481 | width: 14px; 482 | height: 38px; 483 | margin-top: -19px; 484 | background: #808080; 485 | cursor: col-resize; 486 | pointer-events: all; 487 | border-top-right-radius: 3px; 488 | border-bottom-right-radius: 3px; 489 | } 490 | 491 | .iframe-handle-container .tab-handle:before, 492 | .iframe-handle-container .tab-handle:after { 493 | content: ""; 494 | position: absolute; 495 | top: 8px; 496 | bottom: 8px; 497 | width: 1px; 498 | background: #a6a6a6; 499 | } 500 | 501 | .iframe-handle-container .tab-handle:before { 502 | left: 5px; 503 | } 504 | 505 | .iframe-handle-container .tab-handle:after { 506 | left: 8px; 507 | } 508 | 509 | .iframe-handle-container .tab-handle:hover { 510 | background: #178df7; 511 | } 512 | 513 | .iframe-handle-container .tab-handle:hover:before, 514 | .iframe-handle-container .tab-handle:hover:after { 515 | background: #48a5f9; 516 | } 517 | 518 | .iframe-handle-container .gutter-handle { 519 | position: absolute; 520 | top: 0; 521 | left: -3px; 522 | width: 4px; 523 | height: 100%; 524 | cursor: col-resize; 525 | pointer-events: all; 526 | } 527 | 528 | .iframe-handle-container:hover .handle:before { 529 | display: block; 530 | } 531 | 532 | .iframe-handle-container:hover .tab-handle { 533 | background: #178df7; 534 | border-color: #178df7; 535 | } 536 | 537 | .iframe-handle-container:hover .tab-handle:before, 538 | .iframe-handle-container:hover .tab-handle:after { 539 | background: #48a5f9; 540 | } 541 | 542 | .dim-indicator { 543 | position: absolute; 544 | top: 10%; 545 | height: auto; 546 | margin-left: 22px; 547 | pointer-events: all; 548 | font-size: 14px; 549 | display: none; 550 | padding: 2px 9px; 551 | border-radius: 4px !important; 552 | color: #ffffff; 553 | /* font-weight: bold; */ 554 | width: 156px; 555 | background-color: rgb(0, 0, 0); 556 | } --------------------------------------------------------------------------------