diff --git a/file-group.js b/file-group.js
new file mode 100644
index 0000000..a1132a3
--- /dev/null
+++ b/file-group.js
@@ -0,0 +1,88 @@
+export class FileGroup extends HTMLElement {
+ textEn = {
+ addFile: 'Add File',
+ }
+
+ textEs = {
+ addFile: 'AƱadir archivo',
+ }
+
+ constructor() {
+ super()
+ this.language = navigator.language
+ this.attachShadow({mode: 'open'})
+ this.headerEl = document.createElement('div')
+ this.headerEl.classList.add('header')
+ this.contentEl = document.createElement('div')
+ this.contentEl.classList.add('content')
+ this.shadowRoot.appendChild(this.headerEl)
+ this.shadowRoot.appendChild(this.contentEl)
+ const bGroup = document.createElement(
+ 'm-forms-button-group'
+ )
+ bGroup.addPrimary(this.text.addFile, () => {
+ this.addFile()
+ const btn = bGroup.primary
+ if (btn.scrollIntoViewIfNeeded) {
+ btn.scrollIntoViewIfNeeded()
+ } else {
+ btn.scrollIntoView()
+ }
+ })
+ this.shadowRoot.appendChild(bGroup)
+ }
+
+ connectedCallback() {
+ const style = document.createElement('style')
+ style.textContent = `
+ :host {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ }
+ div.header {
+ display: flex;
+ flex-direction: row;
+ }
+ div.files {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ overflow-y: auto;
+ }
+ `
+ this.shadowRoot.appendChild(style)
+ if (this.contentEl.childNodes.length === 0) {
+ this.addFile()
+ }
+ }
+
+ addFile({name, data} = {}) {
+ const el = document.createElement('m-editor-file-view')
+ if (name !== undefined) {
+ el.name = name
+ }
+ if (data !== undefined) {
+ el.data = data
+ }
+ this.contentEl.appendChild(el)
+ return el
+ }
+
+ 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)
+ }
+
+ get files() {
+ return [...this.contentEl.children]
+ }
+}
\ No newline at end of file
diff --git a/file-view.js b/file-view.js
new file mode 100644
index 0000000..1522f0a
--- /dev/null
+++ b/file-view.js
@@ -0,0 +1,120 @@
+export class FileView extends HTMLElement {
+ icons = {
+ menu: `
+
+ `,
+ }
+
+ textEn = {
+ delete: 'Delete',
+ }
+
+ textEs = {
+ delete: 'Borrar',
+ }
+
+ constructor() {
+ super()
+ this.language = navigator.language
+ this.attachShadow({mode: 'open'})
+ this.headerEl = document.createElement('div')
+ this.headerEl.classList.add('header')
+ this.contentEl = document.createElement('div')
+ this.contentEl.classList.add('content')
+ this.shadowRoot.appendChild(this.headerEl)
+ this.shadowRoot.appendChild(this.contentEl)
+ this.nameEl = document.createElement('input')
+ this.nameEl.classList.add('name')
+ this.headerEl.appendChild(this.nameEl)
+ this.editEl = document.createElement('m-editor-text-edit')
+ this.contentEl.appendChild(this.editEl)
+ this.menuBtn = document.createElement('button')
+ this.menuBtn.innerHTML = this.icons.menu
+ this.menuBtn.addEventListener('click', () => {
+ this.menu.open(this.menuBtn)
+ })
+ this.headerEl.appendChild(this.menuBtn)
+ this.menu = document.createElement(
+ 'm-menu-dropdown'
+ )
+ this.menu.add('Borrar', () => {
+ this.remove()
+ })
+ this.shadowRoot.appendChild(this.menu)
+ }
+
+ connectedCallback() {
+ const style = document.createElement('style')
+ style.textContent = `
+ :host {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ }
+ div.header {
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ background-color: #111;
+ color: #ddd;
+ padding: 3px 0;
+ }
+ div.header > * {
+ background: inherit;
+ color: inherit;
+ border: none;
+ }
+ .name {
+ flex-grow: 1;
+ padding: 0 5px;
+ font: inherit;
+ font-family: monospace;
+ outline: none;
+ }
+ div.header button svg {
+ margin-bottom: -3px;
+ }
+ div.content {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ }
+ svg {
+ height: 20px;
+ width: 20px;
+ }
+ `
+ this.shadowRoot.appendChild(style)
+ }
+
+ set name(name) {
+ this.nameEl.value = name
+ }
+
+ get name() {
+ return this.nameEl.value
+ }
+
+ set data(data) {
+ this.editEl.value = data
+ }
+
+ get data() {
+ return this.editEl.value
+ }
+
+ 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)
+ }
+}
\ No newline at end of file
diff --git a/index.html b/index.html
deleted file mode 100644
index 8201026..0000000
--- a/index.html
+++ /dev/null
@@ -1,244 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/text-edit.js b/text-edit.js
new file mode 100644
index 0000000..f32db0b
--- /dev/null
+++ b/text-edit.js
@@ -0,0 +1,58 @@
+export class TextEdit extends HTMLElement {
+ constructor() {
+ super()
+ this.attachShadow({mode: 'open'})
+ this.stackEl = document.createElement('div')
+ this.stackEl.classList.add('stack')
+ this.textEl = document.createElement('textarea')
+ this.textEl.classList.add('text')
+ this.textEl.rows = 1
+ this.stackEl.appendChild(this.textEl)
+ this.shadowRoot.appendChild(this.stackEl)
+ this.textEl.addEventListener('input', () => {
+ this.stackEl.dataset.copy = this.textEl.value
+ })
+ }
+
+ connectedCallback() {
+ // https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/
+ const style = document.createElement('style')
+ style.textContent = `
+ :host {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ margin: 5px 0;
+ }
+ div.stack {
+ display: grid;
+ }
+ div.stack::after {
+ content: attr(data-copy) " ";
+ visibility: hidden;
+ overflow: hidden;
+ }
+ div.stack::after, div.stack > textarea {
+ white-space: pre-wrap;
+ border: 1px solid #888;
+ padding: 3px;
+ font: inherit;
+ font-family: monospace;
+ grid-area: 1 / 1 / 2 / 2;
+ min-height: 1em;
+ border-radius: 2px;
+ resize: none;
+ }
+ `
+ this.shadowRoot.appendChild(style)
+ }
+
+ set value(value) {
+ this.textEl.value = value
+ this.stackEl.dataset.copy = this.textEl.value
+ }
+
+ get value() {
+ return this.textEl.value
+ }
+}
\ No newline at end of file