diff --git a/crochet/locale/en/LC_MESSAGES/django.po b/crochet/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000..50a83a7 --- /dev/null +++ b/crochet/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,183 @@ +msgid "" +msgstr "" +"Project-Id-Version: crochet\n" +"Report-Msgid-Bugs-To: \n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# crochet/templates/crochet/pattern.html + +msgid "No se ha podido guardar. Inténtalo de nuevo." +msgstr "Couldn't save. Please try again." + +msgid "Guardar" +msgstr "Save" + +msgid "Añadir sección" +msgstr "Add section" + +msgid "¿Seguro que quieres cargar el patrón guardado? Se perderán los cambios que no hayas guardado." +msgstr "Are you sure you want to load the saved pattern? Any unsaved changes will be lost." + +msgid "Cargar" +msgstr "Load" + +msgid "Exportar a PDF" +msgstr "Export to PDF" + +msgid "Guardado" +msgstr "Saved" + +msgid "Personalización de página" +msgstr "Page customization" + +msgid "Título del patrón" +msgstr "Pattern title" + +msgid "Título del patrón..." +msgstr "Pattern title..." + +msgid "Autor" +msgstr "Author" + +msgid "Autor..." +msgstr "Author..." + +msgid "Color del texto" +msgstr "Text color" + +msgid "Color de acento (títulos)" +msgstr "Accent color (headings)" + +msgid "Color de fondo" +msgstr "Background color" + +msgid "Tipografía" +msgstr "Font" + +msgid "Monoespaciada" +msgstr "Monospace" + +msgid "Tamaño de letra" +msgstr "Font size" + +msgid "Pequeño" +msgstr "Small" + +msgid "Normal" +msgstr "Normal" + +msgid "Grande" +msgstr "Large" + +msgid "Alineación del texto" +msgstr "Text alignment" + +msgid "Izquierda" +msgstr "Left" + +msgid "Centrado" +msgstr "Center" + +msgid "Justificado" +msgstr "Justify" + +msgid "Tamaño de página" +msgstr "Page size" + +msgid "Carta (Letter)" +msgstr "Letter" + +msgid "Orientación" +msgstr "Orientation" + +msgid "Vertical" +msgstr "Portrait" + +msgid "Horizontal" +msgstr "Landscape" + +msgid "Secciones" +msgstr "Sections" + +msgid "Vista previa" +msgstr "Preview" + +msgid "Colapsar todo" +msgstr "Collapse all" + +msgid "Expandir todo" +msgstr "Expand all" + +msgid "Título" +msgstr "Title" + +msgid "Título..." +msgstr "Title..." + +msgid "Subtítulo" +msgstr "Subtitle" + +msgid "Subtítulo..." +msgstr "Subtitle..." + +msgid "Texto" +msgstr "Text" + +msgid "Escribe aquí..." +msgstr "Write here..." + +msgid "Nota" +msgstr "Note" + +msgid "Escribe una nota o consejo..." +msgstr "Write a note or tip..." + +msgid "Materiales" +msgstr "Materials" + +msgid "Añadir material..." +msgstr "Add material..." + +msgid "Añadir línea" +msgstr "Add line" + +msgid "Imagen" +msgstr "Image" + +msgid "No se ha podido subir la imagen. Inténtalo de nuevo." +msgstr "Couldn't upload the image. Please try again." + +msgid "Patrón" +msgstr "Pattern" + +msgid "Elementos" +msgstr "Elements" + +msgid "Grupo" +msgstr "Group" + +msgid "Arrastrar para reordenar" +msgstr "Drag to reorder" + +msgid "Colapsar / expandir" +msgstr "Collapse / expand" + +msgid "Duplicar sección" +msgstr "Duplicate section" + +msgid "Eliminar sección" +msgstr "Delete section" + +msgid "¿Seguro que quieres eliminar este grupo? Se eliminarán también todas las secciones que contiene." +msgstr "Are you sure you want to delete this group? All sections inside it will also be deleted." + +msgid "Añade una sección para ver aquí el resultado." +msgstr "Add a section to see the result here." + +# crochet/views.py (PatternDetailView.EMPTY_MESSAGE) + +msgid "Este patrón todavía no tiene contenido." +msgstr "This pattern doesn't have any content yet." diff --git a/crochet/static/js/main.js b/crochet/static/js/main.js index 8c18078..34caf10 100644 --- a/crochet/static/js/main.js +++ b/crochet/static/js/main.js @@ -5,9 +5,13 @@ import { renderOutput } from './render.js'; import { renderPageMeta, applyPageSettings } from './page-settings.js'; import { applyDefaultSkeleton, buildSectionsFrom } from './section-types.js'; +import { setAllSectionsCollapsed } from './sections.js'; import './tabs.js'; import './storage.js'; +// Mismo punto de corte que .panel-hidden-mobile (ver css/main.css). +const MOBILE_BREAKPOINT_QUERY = '(max-width: 767px)'; + // El propio patrón (sections/pageSettings ya guardados) viaja embebido en la // plantilla Django, ver {{ pattern_data|json_script:"pattern-data" }} en // pattern.html. Si todavía no tiene ninguna sección (patrón recién creado), @@ -25,5 +29,14 @@ function loadInitialPattern() { } loadInitialPattern(); + +// En pantallas estrechas arranca con todas las secciones colapsadas (solo +// se ve el badge de cada una) en vez de todos sus campos editables a la +// vez: con un patrón ya guardado (varias secciones o grupos) ese "ruido" +// inicial era mucho en móvil. +if (window.matchMedia(MOBILE_BREAKPOINT_QUERY).matches) { + setAllSectionsCollapsed(true); +} + renderPageMeta(); renderOutput(); diff --git a/crochet/static/js/section-types.js b/crochet/static/js/section-types.js index 16e5681..1d13a4e 100644 --- a/crochet/static/js/section-types.js +++ b/crochet/static/js/section-types.js @@ -54,17 +54,38 @@ function addGroupSection(data, children = [], container = sectionsContainer) { const groupContainer = document.createElement('div'); groupContainer.className = 'group-container flex flex-col gap-4'; - const toolbar = document.createElement('div'); - toolbar.className = 'flex flex-wrap gap-2 mb-4'; + // Mismo menú desplegable que la barra superior (ver pattern.html), no una + // fila de botones sueltos: un grupo anidado multiplicaba ese ruido visual + // en vez de reducirlo. + const dropdown = document.createElement('div'); + dropdown.className = 'dropdown mb-2'; + + const dropdownBtn = document.createElement('button'); + dropdownBtn.type = 'button'; + dropdownBtn.className = 'btn btn-sm'; + dropdownBtn.tabIndex = 0; + dropdownBtn.textContent = `+ ${addSectionLabel}`; + + const menu = document.createElement('ul'); + menu.tabIndex = 0; + menu.className = 'dropdown-content menu menu-sm bg-base-100 border border-base-300 rounded-box z-10 w-52 p-2 shadow'; SECTION_TYPES.forEach(({ button, create }) => { const btn = button.cloneNode(true); btn.removeAttribute('id'); // Puede haber varios grupos, cada uno con su propia copia de estos botones. - btn.addEventListener('click', () => create(undefined, groupContainer)); - toolbar.appendChild(btn); + btn.addEventListener('click', () => { + create(undefined, groupContainer); + btn.blur(); // Cierra el menú (daisyUI lo abre con :focus-within) tras elegir. + }); + const item = document.createElement('li'); + item.appendChild(btn); + menu.appendChild(item); }); - body.appendChild(toolbar); + dropdown.appendChild(dropdownBtn); + dropdown.appendChild(menu); + + body.appendChild(dropdown); body.appendChild(groupContainer); container.appendChild(section); @@ -73,6 +94,11 @@ function addGroupSection(data, children = [], container = sectionsContainer) { buildSectionsFrom(children, groupContainer); } +// Etiqueta de "+ Añadir sección" compartida por la barra superior y por el +// menú propio de cada "Grupo" (ver addGroupSection): un solo dato en el +// HTML (ver pattern.html), traducible por la plantilla Django. +const addSectionLabel = document.getElementById('add-section-toggle').dataset.label; + const addTitleBtn = document.getElementById('add-title-section'); const addSubtitleBtn = document.getElementById('add-subtitle-section'); const addTextBtn = document.getElementById('add-text-section'); @@ -432,9 +458,12 @@ sectionsContainer.addEventListener('section:duplicate', (e) => duplicateSection( // addGroupSection() para el de dentro de un grupo). setupSectionsReorder(sectionsContainer, panelTop); -// Botones de la barra superior para añadir cada tipo de sección. Los textos +// Botones del menú "+ Añadir sección" de la barra superior. Los textos // (etiqueta de tipo, placeholders...) se leen de sus data-* en el HTML, no // están escritos aquí, para que la plantilla Django pueda traducirlos. SECTION_TYPES.forEach(({ button, create }) => { - button.addEventListener('click', () => create(undefined, undefined)); + button.addEventListener('click', () => { + create(undefined, undefined); + button.blur(); // Cierra el menú (daisyUI lo abre con :focus-within) tras elegir. + }); }); diff --git a/crochet/static/js/sections.js b/crochet/static/js/sections.js index e80fda3..af44571 100644 --- a/crochet/static/js/sections.js +++ b/crochet/static/js/sections.js @@ -380,8 +380,24 @@ export function addImageSection(data, image = null, container = sectionsContaine export function addDndSection(data, items = [], container = sectionsContainer) { const { section, body } = createSectionShell(data.typeLabel); - const row = document.createElement('div'); - row.className = 'flex flex-col sm:flex-row gap-4'; + // Siempre en columna (antes cambiaba a fila a partir de "sm", con los + // elementos arrastrables a la derecha): con el catálogo ya grande, esa + // barra lateral quedaba demasiado estrecha para envolver los tipos de + // punto con comodidad, así que ahora va arriba a todo lo ancho. + const column = document.createElement('div'); + column.className = 'flex flex-col gap-4'; + + const elementsPanel = document.createElement('div'); + elementsPanel.className = 'w-full border border-base-300 rounded-box p-4'; + + const elementsTitle = document.createElement('h3'); + elementsTitle.className = 'font-semibold mb-2'; + elementsTitle.textContent = data.elementsLabel; + + const list = createElementsList(); + + elementsPanel.appendChild(elementsTitle); + elementsPanel.appendChild(list); const dndCanvas = document.createElement('div'); // content-start (align-content: flex-start): sin esto, un flex-wrap con @@ -393,21 +409,9 @@ export function addDndSection(data, items = [], container = sectionsContainer) { // debajo, no repartido. dndCanvas.className = 'dnd-canvas flex-1 flex flex-wrap content-start items-center gap-2 min-h-24 py-2'; - const elementsPanel = document.createElement('aside'); - elementsPanel.className = 'w-full sm:w-48 sm:shrink-0 border border-base-300 rounded-box p-4'; - - const elementsTitle = document.createElement('h3'); - elementsTitle.className = 'font-semibold mb-2'; - elementsTitle.textContent = data.elementsLabel; - - const list = createElementsList(); - - elementsPanel.appendChild(elementsTitle); - elementsPanel.appendChild(list); - - row.appendChild(dndCanvas); - row.appendChild(elementsPanel); - body.appendChild(row); + column.appendChild(elementsPanel); + column.appendChild(dndCanvas); + body.appendChild(column); container.appendChild(section); setupDropZone(dndCanvas, undefined, section); @@ -418,21 +422,26 @@ export function addDndSection(data, items = [], container = sectionsContainer) { // Colapsar / expandir todas las secciones de una vez // --------------------------------------------------------------------- +const toggleCollapseAllBtn = document.getElementById('toggle-collapse-all'); + // Colapsa o expande a la vez todas las secciones que haya, sin importar su // nivel de anidamiento (querySelectorAll recorre también las que están // dentro de un "Grupo"), sincronizando tanto su cuerpo como el icono de su -// propio botón de colapsar individual. -function setAllSectionsCollapsed(collapsed) { +// propio botón de colapsar individual y la etiqueta de "Colapsar todo" / +// "Expandir todo". Exportada para poder arrancar ya colapsado en móvil (ver +// main.js), no solo desde el propio botón. +export function setAllSectionsCollapsed(collapsed) { sectionsContainer.querySelectorAll('.section-body').forEach(body => { body.classList.toggle('hidden', collapsed); }); sectionsContainer.querySelectorAll('.collapse-btn').forEach(btn => { btn.textContent = collapsed ? '▸' : '▾'; }); + toggleCollapseAllBtn.textContent = collapsed + ? toggleCollapseAllBtn.dataset.expandAllLabel + : toggleCollapseAllBtn.dataset.collapseAllLabel; } -const toggleCollapseAllBtn = document.getElementById('toggle-collapse-all'); - toggleCollapseAllBtn.addEventListener('click', () => { // Se decide según el estado actual (en vez de recordar uno propio) para // que el botón siga teniendo sentido aunque el usuario haya colapsado o @@ -441,7 +450,4 @@ toggleCollapseAllBtn.addEventListener('click', () => { const anyExpanded = Array.from(sectionsContainer.querySelectorAll('.section-body')) .some(body => !body.classList.contains('hidden')); setAllSectionsCollapsed(anyExpanded); - toggleCollapseAllBtn.textContent = anyExpanded - ? toggleCollapseAllBtn.dataset.expandAllLabel - : toggleCollapseAllBtn.dataset.collapseAllLabel; }); diff --git a/crochet/templates/crochet/pattern.html b/crochet/templates/crochet/pattern.html index 8ab5b3e..09a5e73 100644 --- a/crochet/templates/crochet/pattern.html +++ b/crochet/templates/crochet/pattern.html @@ -1,4 +1,4 @@ -{% load static %} +{% load static i18n %} @@ -34,10 +34,10 @@ + data-error-message="{% trans 'No se ha podido guardar. Inténtalo de nuevo.' %}">{% trans 'Guardar' %} - + data-confirm-message="{% trans '¿Seguro que quieres cargar el patrón guardado? Se perderán los cambios que no hayas guardado.' %}">{% trans 'Cargar' %} + @@ -45,7 +45,7 @@ localStorage no da ninguna señal de que ha ocurrido algo. -->
@@ -56,72 +56,72 @@ de #panel-top-sections.