export class Header extends HTMLElement { icons = { menu: ``, dot: ``, edit: ``, check: ``, add: ``, } textEn = { confirmDelete: f => ( `Are you sure you want to delete ${f}?` ), cancel: 'Cancel', alreadyExists: 'There is already a page with that name.', createPage: 'Create Page', htmlCss: 'HTML/CSS', singleFile: 'Single File', newPage: 'New Page', go: 'Go', } textEs = { confirmDelete: f => ( `¿Desea borrar ${f}?` ), cancel: 'Cancelar', alreadyExists: 'Ya existe una página con ese nombre.', createPage: 'Crear Página', htmlCss: 'HTML/CSS', singleFile: 'Archivo único', newPage: 'Nueva Página', go: 'Ir', } 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) const navBtn = this.addButton( this.icons.menu, 'nav', () => this.openNav(), ) navBtn.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', () => { const actions = this.pageActions.menuActions this.pageMenu.clear() for (const {text, click} of actions) { this.pageMenu.add(text, click) } this.pageMenu.open(this.pageButton) } ) this.pageButton.classList.add('right-end') this.addMenu() } 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-left: 12px; } button.right-end { padding-right: 12px; } div.menu { position: fixed; top: 0; left: max(-90vw, -480px); height: 100vh; width: min(90vw, 480px); 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; } ` this.shadowRoot.append(style) this.addPageMenu() } 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() }) this.menu.addEventListener( 'click-location', () => { this.close() this.navPage() }, ) } addPageMenu() { this.pageMenu = document.createElement( 'm-menu-dropdown' ) this.shadowRoot.appendChild(this.pageMenu) } openNav() { this.menu.location = this.path this.menu.pages = this.getPages() this.menu.storageUse = this.getStorageUse() this.menuPanel.classList.add('open') this.overlay.classList.add('open') } 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 ( this.storage.keys().slice() .filter(s => s.startsWith('/')) .sort() ) } getStorageUse() { let bytes = 0 const entries = this.storage.entries() for (const [k, v] of entries) { bytes += k.length bytes += v.length } return bytes / 5000000 } 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 select = document.createElement('select') const options = ['htmlCss', 'singleFile'] select.append(...options.map(value => { const el = document.createElement('option') el.value = value el.innerText = this.text[value] return el })) select.value = 'htmlCss' input.addEventListener('input', e => { const ext = e.target.value.match(/\.\w+$/) select.value = ext ? 'singleFile' : 'htmlCss' }) select.style.marginTop = '10px' select.style.marginBottom = '10px' dialog.bodyEl.appendChild(select) let errorEl const bGroup = document.createElement( 'm-forms-button-group' ) bGroup.addPrimary(this.text.createPage, () => { const newPath = this.encodePath(input.value) const v = this.storage.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 } return } const value = ( select.value === 'singleFile' ? '' : JSON.stringify({ type: 'm-file-group', files: [ { "name": "index.html", "data": `