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`
${unsafeHTML(main)}
`, 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`
this.onDrop(event)}>
${ // keep the same structure as the layers panel symbols .filter(s => s.main) .map(symbolData => html`
this.onRemove(event)}>
${symbolData.main!.getName()}
${symbolData.instances.length} instances
`) } ${symbols.length ? '' : html`
${ this.options.emptyText }
`}
`, 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: `

Are you sure you want to delete this symbol?

Deleting this symbol will not delete its instances, just disconnects them. Confirm to proceed or cancel to maintain the current link.

`, 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) } }