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`
${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)
}
}