@erplora/outfitkit
Advanced tools
| import './ok-file-manager'; |
@@ -55,2 +55,18 @@ import { LitElement } from 'lit'; | ||
| /** Textos i18n del componente (defaults en español). */ | ||
| /** | ||
| * Qué se puede hacer en la carpeta actual. La decide el HOST (en ERPlora, el módulo dueño de la | ||
| * carpeta — ADR-0172) y el componente solo la obedece al pintar: **no es una barrera de | ||
| * seguridad**, el servidor revalida siempre. Ver y descargar no están aquí porque son siempre | ||
| * posibles: "solo lectura" no significa "no puedes mirar". | ||
| * | ||
| * Ausente ⇒ todo disponible (los consumidores que no la pasan no cambian de comportamiento). | ||
| */ | ||
| export interface OkFmPolicy { | ||
| /** Subir ficheros y crear subcarpetas. */ | ||
| upload: boolean; | ||
| /** Renombrar ficheros y la propia carpeta. */ | ||
| rename: boolean; | ||
| /** Borrar ficheros y la propia carpeta. */ | ||
| delete: boolean; | ||
| } | ||
| export interface OkFmLabels { | ||
@@ -77,2 +93,8 @@ /** Botón primario de subida. */ | ||
| newFolder: string; | ||
| /** Acción de renombrar (ficheros y carpetas). */ | ||
| rename: string; | ||
| /** Renombrar la carpeta en la que se está. */ | ||
| renameFolder: string; | ||
| /** Borrar la carpeta en la que se está (con su contenido). */ | ||
| deleteFolder: string; | ||
| /** Caption del medidor cuando no hay cuota dura. */ | ||
@@ -101,2 +123,5 @@ noLimit: string; | ||
| uploadable: boolean; | ||
| /** Acciones permitidas en la carpeta actual (ver `OkFmPolicy`). Ausente ⇒ todo permitido. */ | ||
| policy?: OkFmPolicy; | ||
| private can; | ||
| /** Muestra esqueletos de carga en lugar del contenido. */ | ||
@@ -118,2 +143,8 @@ loading: boolean; | ||
| private deleteFile; | ||
| /** Renombrar un fichero. Se manda el nombre actual para que el host lo proponga en su diálogo. */ | ||
| private renameFile; | ||
| /** Etiqueta de la carpeta actual (para proponerla como nombre al renombrar). */ | ||
| private get currentFolderLabel(); | ||
| private renameCurrentFolder; | ||
| private deleteCurrentFolder; | ||
| private createFolder; | ||
@@ -138,2 +169,3 @@ private changeView; | ||
| private get iconDownload(); | ||
| private get iconRename(); | ||
| private get iconDelete(); | ||
@@ -140,0 +172,0 @@ private fileActions; |
+100
-25
@@ -25,2 +25,5 @@ import { LitElement, css, html } from "lit"; | ||
| newFolder: "Nueva carpeta", | ||
| rename: "Renombrar", | ||
| renameFolder: "Renombrar carpeta", | ||
| deleteFolder: "Eliminar carpeta", | ||
| noLimit: "Sin límite" | ||
@@ -549,2 +552,7 @@ }; | ||
| } | ||
| /* Borrar la carpeta actual: se tiñe al pasar por encima, igual que el borrado de un fichero. */ | ||
| .tbtn.danger:hover { | ||
| color: var(--danger); | ||
| border-color: var(--danger); | ||
| } | ||
| .action svg { | ||
@@ -649,2 +657,5 @@ width: 15px; | ||
| } | ||
| can(action) { | ||
| return this.policy ? this.policy[action] : true; | ||
| } | ||
| // Textos efectivos. | ||
@@ -677,4 +688,18 @@ get t() { | ||
| deleteFile(id) { | ||
| this.emit("ok-delete", { id }); | ||
| this.emit("ok-delete", { id, kind: "file" }); | ||
| } | ||
| /** Renombrar un fichero. Se manda el nombre actual para que el host lo proponga en su diálogo. */ | ||
| renameFile(file) { | ||
| this.emit("ok-rename", { id: file.id, name: file.name, kind: "file" }); | ||
| } | ||
| /** Etiqueta de la carpeta actual (para proponerla como nombre al renombrar). */ | ||
| get currentFolderLabel() { | ||
| return this.path[this.path.length - 1]?.label ?? this.selected.split("/").pop() ?? ""; | ||
| } | ||
| renameCurrentFolder() { | ||
| this.emit("ok-rename", { id: this.selected, name: this.currentFolderLabel, kind: "folder" }); | ||
| } | ||
| deleteCurrentFolder() { | ||
| this.emit("ok-delete", { id: this.selected, kind: "folder" }); | ||
| } | ||
| createFolder() { | ||
@@ -884,19 +909,43 @@ this.emit("ok-create-folder", { parent: this.selected }); | ||
| <button | ||
| type="button" | ||
| class="tbtn icon" | ||
| aria-label=${this.t.newFolder} | ||
| title=${this.t.newFolder} | ||
| @click=${() => this.createFolder()} | ||
| > | ||
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" | ||
| stroke-linecap="round" stroke-linejoin="round"> | ||
| <path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> | ||
| <path d="M12 11v6M9 14h6" /> | ||
| </svg> | ||
| </button> | ||
| ${this.selected && this.can("rename") ? html`<button | ||
| type="button" | ||
| class="tbtn icon" | ||
| data-act="rename-folder" | ||
| aria-label=${this.t.renameFolder} | ||
| title=${this.t.renameFolder} | ||
| @click=${() => this.renameCurrentFolder()} | ||
| > | ||
| ${this.iconRename} | ||
| </button>` : ""} | ||
| ${this.uploadable ? html`<button | ||
| ${this.selected && this.can("delete") ? html`<button | ||
| type="button" | ||
| class="tbtn icon danger" | ||
| data-act="delete-folder" | ||
| aria-label=${this.t.deleteFolder} | ||
| title=${this.t.deleteFolder} | ||
| @click=${() => this.deleteCurrentFolder()} | ||
| > | ||
| ${this.iconDelete} | ||
| </button>` : ""} | ||
| ${this.can("upload") ? html`<button | ||
| type="button" | ||
| class="tbtn icon" | ||
| data-act="new-folder" | ||
| aria-label=${this.t.newFolder} | ||
| title=${this.t.newFolder} | ||
| @click=${() => this.createFolder()} | ||
| > | ||
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" | ||
| stroke-linecap="round" stroke-linejoin="round"> | ||
| <path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" /> | ||
| <path d="M12 11v6M9 14h6" /> | ||
| </svg> | ||
| </button>` : ""} | ||
| ${this.uploadable && this.can("upload") ? html`<button | ||
| type="button" | ||
| class="tbtn primary icon" | ||
| data-act="upload" | ||
| aria-label=${this.t.upload} | ||
@@ -928,2 +977,8 @@ title=${this.t.upload} | ||
| } | ||
| get iconRename() { | ||
| return html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" | ||
| stroke-linecap="round" stroke-linejoin="round"> | ||
| <path d="M12 20h9" /><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z" /> | ||
| </svg>`; | ||
| } | ||
| get iconDelete() { | ||
@@ -935,3 +990,4 @@ return html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" | ||
| } | ||
| // Acciones de un archivo (abrir/descargar/eliminar), reutilizadas en grid y lista. | ||
| // Acciones de un archivo, reutilizadas en grid y lista. Abrir y descargar SIEMPRE se ofrecen; | ||
| // renombrar y borrar solo si la política de la carpeta los concede. | ||
| fileActions(file) { | ||
@@ -941,2 +997,3 @@ return html`<button | ||
| class="action" | ||
| data-act="open" | ||
| aria-label=${this.t.open} | ||
@@ -954,2 +1011,3 @@ title=${this.t.open} | ||
| class="action" | ||
| data-act="download" | ||
| aria-label=${this.t.download} | ||
@@ -964,14 +1022,28 @@ title=${this.t.download} | ||
| </button> | ||
| <button | ||
| type="button" | ||
| class="action danger" | ||
| aria-label=${this.t.delete} | ||
| title=${this.t.delete} | ||
| @click=${(e) => { | ||
| ${this.can("rename") ? html`<button | ||
| type="button" | ||
| class="action" | ||
| data-act="rename-file" | ||
| aria-label=${this.t.rename} | ||
| title=${this.t.rename} | ||
| @click=${(e) => { | ||
| e.stopPropagation(); | ||
| this.renameFile(file); | ||
| }} | ||
| > | ||
| ${this.iconRename} | ||
| </button>` : ""} | ||
| ${this.can("delete") ? html`<button | ||
| type="button" | ||
| class="action danger" | ||
| data-act="delete-file" | ||
| aria-label=${this.t.delete} | ||
| title=${this.t.delete} | ||
| @click=${(e) => { | ||
| e.stopPropagation(); | ||
| this.deleteFile(file.id); | ||
| }} | ||
| > | ||
| ${this.iconDelete} | ||
| </button>`; | ||
| > | ||
| ${this.iconDelete} | ||
| </button>` : ""}`; | ||
| } | ||
@@ -1110,2 +1182,5 @@ // ---- Render del contenido (grid/lista/vacío/loading) ---- | ||
| __decorateClass([ | ||
| property({ attribute: false }) | ||
| ], OkFileManager.prototype, "policy"); | ||
| __decorateClass([ | ||
| property({ type: Boolean }) | ||
@@ -1112,0 +1187,0 @@ ], OkFileManager.prototype, "loading"); |
+1
-1
| { | ||
| "name": "@erplora/outfitkit", | ||
| "version": "0.1.28", | ||
| "version": "0.1.29", | ||
| "description": "OutfitKit — librería de Web Components (Lit) que CONSTRUYE lo que Ionic no tiene (tree, data-table rica, inline-feedback, kpi/stat, stepper/wizard, calendar, kanban…) sobre primitivos de Ionic. Ionic es la base; OutfitKit cubre los huecos. npm + CDN, imports individuales, CSP-safe. Tema vía tokens --ok-* (fallback a --ion-*).", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+1
-1
@@ -442,3 +442,3 @@ # OutfitKit (`@erplora/outfitkit`) | ||
| npm run dev # vite build --watch (showcase en local) | ||
| npm run release # release-it: corta una versión (ver docs/RELEASING.md) | ||
| npm run release # release-it a mano (NO hace falta: la CI publica al mergear, ver docs/RELEASING.md) | ||
| ``` | ||
@@ -445,0 +445,0 @@ |
Sorry, the diff of this file is too big to display
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
2092450
0.34%288
0.35%60628
0.29%