UNPKG

@silexlabs/grapesjs-symbols

Version:
252 lines (243 loc) 8.29 kB
import { render, html } from 'lit-html' import { unsafeHTML } from 'lit-html/directives/unsafe-html.js' import { Editor } from 'grapesjs' import { allowDrop, createSymbol, deleteSymbol, getSymbol, getSymbols } from '../utils' function closestHtml(child: HTMLElement, attr: string) { let ptr: HTMLElement | null = child while(ptr && !ptr.getAttribute(attr)) { ptr = ptr.parentElement } return ptr } export function confirmDialog({ editor, content: main, title, primaryLabel, secondaryLabel = 'Cancel', cbk, lsKey, }: { editor: Editor, content: string, title: string, primaryLabel: string, secondaryLabel?: string, cbk: () => void, lsKey: string, }) { // Check if the user has already been asked if (localStorage.getItem(lsKey) === 'on') { cbk() } else { const content = document.createElement('div') editor.Modal.open({ title, content, }) let remember = 'off' render(html`<main> ${unsafeHTML(main)} </main><footer style=" display: flex; justify-content: space-between; margin-top: 30px; "> <div> <label class="gjs-field gjs-field-checkbox" style=" float: left; margin-right: 10px; "> <input type="checkbox" id="remember" @click=${({ target: rememberCheckbox }: MouseEvent) => remember = (rememberCheckbox as HTMLInputElement)?.value}> <i class="gjs-chk-icon"></i> </label> <label for="remember">Don't ask me again</label> </div> <div> <button class="gjs-btn-prim" @click=${() => editor.Modal.close()} style=" margin-left: auto; background: transparent; margin-right: 10px; ">${secondaryLabel}</button> <button class="gjs-btn-prim" @click=${() => { cbk() localStorage.setItem(lsKey, remember) editor.Modal.close() }}>${primaryLabel}</button> </div> </footer>`, content) } } export interface SymbolsViewOptions { editor: Editor, appendTo: string, highlightColor: string, emptyText: string, } export default class SymbolsView { protected lastPos: any | null = null protected lastTarget: HTMLElement | null = null protected el: HTMLElement constructor(protected options: SymbolsViewOptions) { // listen to redraw UI options.editor.on('component:selected', () => this.render()) // listen to drag event in order to have access to the drop target options.editor.on('sorter:drag', (event: any) => { this.lastPos = event.pos this.lastTarget = event.target }) // Listen to events on `symbol` options.editor.on('symbol', () => { // Redraw the UI this.render() }) // list wrapper this.el = document.createElement('div') this.el.classList.add('symbols__wrapper') document.querySelector(options.appendTo)! .appendChild(this.el) // first render this.render() } render() { const symbols = getSymbols(this.options.editor) symbols.forEach(symbolInfo => { if (!symbolInfo.main) { console.warn('Symbol has no main component:', symbolInfo) return } }) const selected = this.options.editor.getSelected() render(html` <style> .symbols__symbol-selected { border: 1px solid ${ this.options.highlightColor }; } .symbols__symbol { position: relative; } .symbols__num { font-size: xx-small; } .symbols__empty { padding: 10px; text-align: center; width: 100%; } .symbols__remove { position: absolute; top: 0; right: 0; width: 20px; line-height: 1; cursor: pointer; } </style> <main class="symbols__list" @dragend=${(event: Event) => this.onDrop(event)}> <div class="gjs-blocks-c"> ${ // keep the same structure as the layers panel symbols .filter(s => s.main) .map(symbolData => html` <div class="gjs-block gjs-one-bg gjs-four-color-h symbols__symbol ${!!selected && symbolData.instances.includes(selected) ? 'symbols__symbol-selected' : ''} ${symbolData.main!.get('icon') || 'fa fa-diamond'} " title="" draggable="true" data-symbol-id=${symbolData.main!.getId()} > <div title="Unlink all instances and delete Symbol" class="symbols__remove" @click=${(event: MouseEvent) => this.onRemove(event)}> <svg viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"></path></svg> </div> <div class="gjs-block-label"> ${symbolData.main!.getName()} <div class="symbols__num"> ${symbolData.instances.length} instances </div> </div> </div> `) } ${symbols.length ? '' : html`<div class="symbols__empty"> ${ this.options.emptyText } </div>`} </div> </main> `, this.el) return this } onDrop(event: Event) { const symbolId = (event.target! as HTMLElement).dataset.symbolId if(symbolId) { const symbolInfo = getSymbol(this.options.editor, this.options.editor.Components.getById(symbolId)) if(symbolInfo) { const parentId = this.lastTarget?.id if (!parentId) throw new Error('Can not create the symbol: missing param id') const parent = this.lastTarget ? this.options.editor.Components.getById(parentId) : null if (parent) { // Make sure we have a target and position this.lastTarget = this.lastTarget || this.options.editor.Canvas.getBody() this.lastPos = this.lastPos || { placement: 'after', index: 0 } // Get the parent component from the HTML element const parentId = this.lastTarget.getAttribute('id') if (!parentId) throw new Error('Can not create the symbol: missing param id') const parent = this.options.editor.Components.allById()[parentId] // Check if we can drop the symbol there if (allowDrop(this.options.editor, parent)) { // create the new component const {instances} = createSymbol(this.options.editor, symbolInfo.main!)! // Last one is the added one const instance = instances[instances.length - 1] const [c] = this.lastPos.placement === 'after' ? parent.append(instance) : parent.append(instance, { at: this.lastPos.index }) // Select the new component // Break unit tests? editor.select(c, { scroll: true }) return c } else { throw new Error('Can not create the symbol: one of the parent is in the symbol') } } else { throw new Error('Can not create the symbol: parent not found') } } else { throw new Error(`Could not create an instance of symbol ${symbolId}: symbol not found`) } } else { // not a symbol creation } return null } onRemove({ target: deleteButton }: MouseEvent) { // Warn the user confirmDialog({ editor: this.options.editor, title: 'Delete Symbol', content: ` <p>Are you sure you want to delete this symbol?</p> <p>Deleting this symbol <em>will not</em> delete its instances, just disconnects them. Confirm to proceed or cancel to maintain the current link.</p> `, primaryLabel: 'Delete', cbk: () => { this.onRemoveConfirm(deleteButton as HTMLElement) }, lsKey: 'delete-symbol', }) } onRemoveConfirm(target: HTMLElement) { const symbolId = closestHtml((target), 'data-symbol-id') ?.dataset.symbolId if (!symbolId) { throw new Error('Can not delete symbol: missing param symbolId') } const symbol = this.options.editor.Components.getSymbols() .find(symbol => symbol.getId() === symbolId) if (!symbol) { throw new Error('Can not delete symbol: symbol not found') } deleteSymbol(this.options.editor, symbol) } }