export class Header extends HTMLElement { icons = { menu: ` `, dot: ` `, edit: ` `, check: ` `, add: ` `, } textEn = { download: 'Download', rename: 'Move/Rename', delete: 'Delete', confirmDelete: f => ( `Are you sure you want to delete ${f}?` ), cancel: 'Cancel', alreadyExists: 'There is already a page with that name.', createPage: 'Create Page', } textEs = { download: 'Descargar', rename: 'Mover/Renombrar', delete: 'Borrar', confirmDelete: f => ( `¿Desea borrar ${f}?` ), cancel: 'Cancelar', alreadyExists: 'Ya existe una página con ese nombre.', createPage: 'Crear Página', } constructor() { super() this.language = navigator.language this.editing = false this.attachShadow({mode: 'open'}) this.appBar = document.createElement('div') this.appBar.classList.add('app-bar') this.shadowRoot.appendChild(this.appBar) this.addButton(this.icons.menu, 'nav', () => { this.menu.pages = this.getPages() this.menuPanel.classList.add('open') this.overlay.classList.add('open') }).classList.add('left-end') this.addButton(this.icons.add, 'add', () => { this.addPage() }) this.addDivider() this.editBtn = this.addButton(this.editIcon, 'edit', () => { this.dispatchEvent(new CustomEvent( 'click-edit', {bubbles: true} )) }) this.pageButton = this.addButton( this.icons.dot, 'page', () => { this.pageMenu.open(this.pageButton) } ) this.pageButton.classList.add('right-end') this.addPageMenu() this.addMenu() this.dialogWrap = document.createElement('div') this.shadowRoot.appendChild(this.dialogWrap) } connectedCallback() { const style = document.createElement('style') style.textContent = ` :host { background: #111; color: #ddd; display: flex; flex-direction: column; align-items: stretch; } div.app-bar { display: flex; flex-direction: row; padding: 0; } div.divider { flex-grow: 1; } button { border: none; background: inherit; color: inherit; font-size: 30px; padding: 0 8px; } button.left-end { padding-right: 12px; } button.right-end { padding-right: 12px; } div.menu { position: fixed; top: 0; left: -90vw; height: 100vh; width: 90vw; background-color: #fff; transition: left .25s ease-in-out; } div.menu.open { left: 0; } div.overlay { position: fixed; top: 0; left: -100vw; height: 100vh; width: 100vw; opacity: 0%; transition: opacity .25s ease-in; background: #777; } div.overlay.closing { left: 0; } div.overlay.open { left: 0; opacity: 15%; } svg { width: 20px; height: 20px; } m-dialog::part(footer) { padding-top: 15px; } ` this.shadowRoot.append(style) } encodePath(path) { return path } addButton(html, cls, onClick) { const b = document.createElement('button') b.innerHTML = html b.classList.add(cls) this.appBar.appendChild(b) if (onClick) { b.addEventListener('click', onClick) } return b } addDivider() { const d = document.createElement('div') d.classList.add('divider') this.appBar.appendChild(d) } addMenu() { this.overlay = document.createElement('div') this.overlay.classList.add('overlay') this.shadowRoot.appendChild(this.overlay) this.menuPanel = document.createElement('div') this.menuPanel.classList.add('menu') this.menu = document.createElement('m-nav-menu') this.menuPanel.appendChild(this.menu) this.shadowRoot.appendChild(this.menuPanel) this.overlay.addEventListener('click', () => { this.close() }) this.menu.addEventListener('close-menu', () => { this.close() }) } addPageMenu() { this.pageMenu = document.createElement( 'm-menu-dropdown' ) this.pageMenu.add(this.text.download, () => { const text = localStorage.getItem(this.path) const sp = this.path.split('/') const filename = sp[sp.length - 1] const el = document.createElement('a') el.setAttribute( 'href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text) ) el.setAttribute('download', filename) el.style.display = 'none' this.shadowRoot.appendChild(el) el.click() this.shadowRoot.removeChild(el) }) this.pageMenu.add(this.text.rename, () => { const dialog = document.createElement( 'm-dialog' ) this.dialogWrap.replaceChildren(dialog) const input = document.createElement('input') input.value = this.path input.style.minWidth = '300px' dialog.bodyEl.appendChild(input) let errorEl const bGroup = document.createElement( 'm-forms-button-group' ) bGroup.addPrimary(this.text.rename, () => { const newPath = input.value const v = localStorage.getItem(newPath) if (v !== null || newPath === this.path) { if (!errorEl) { errorEl = document.createElement('p') errorEl.style.color = 'red' const errText = this.text.alreadyExists errorEl.innerText = errText dialog.bodyEl.appendChild(errorEl) } return } localStorage.setItem( newPath, localStorage.getItem(this.path) ) localStorage.removeItem(this.path) dialog.close() vlocation.hash = newPath }) bGroup.addCancel(this.text.cancel, () => { dialog.close() }) dialog.footerEl.appendChild(bGroup) dialog.open() }) this.pageMenu.add(this.text.delete, () => { const dialog = document.createElement( 'm-dialog' ) this.dialogWrap.replaceChildren(dialog) const p = document.createElement('p') p.innerText = this.text.confirmDelete( JSON.stringify(this.path) ) dialog.bodyEl.appendChild(p) const bGroup = document.createElement( 'm-forms-button-group' ) bGroup.addPrimary(this.text.delete, () => { localStorage.removeItem(this.path) vlocation.hash = '/' dialog.close() }) bGroup.addCancel(this.text.cancel, () => { dialog.close() }) dialog.footerEl.appendChild(bGroup) dialog.open() }) this.shadowRoot.appendChild(this.pageMenu) } close() { this.overlay.classList.add('closing') this.overlay.classList.remove('open') this.menuPanel.classList.remove('open') setTimeout(() => { this.overlay.classList.remove('closing') }, 250) } getPages() { return Object.keys(localStorage).slice().sort() } set editing(value) { this._editing = value if (this.editBtn) { this.editBtn.innerHTML = this.editIcon } } get editing() { return this._editing } get editIcon() { return this.editing ? this.icons.check : this.icons.edit } addPage() { const dialog = document.createElement('m-dialog') this.dialogWrap.replaceChildren(dialog) const input = document.createElement('input') input.value = '/' input.style.minWidth = '300px' dialog.bodyEl.appendChild(input) const bGroup = document.createElement( 'm-forms-button-group' ) bGroup.addPrimary(this.text.createPage, () => { const newPath = this.encodePath(input.value) vlocation.hash = newPath dialog.close() }) bGroup.addCancel(this.text.cancel, () => { dialog.close() }) dialog.footerEl.appendChild(bGroup) dialog.open() } get language() { return this._language } set language(language) { this._language = language this.text = this.langEs ? this.textEs : this.textEn } get langEs() { return /^es\b/.test(this.language) } }