| @ -0,0 +1,265 @@ | |||||
| package mx.gob.jumapacelaya.ui; | |||||
| import com.vaadin.flow.component.UI; | |||||
| import com.vaadin.flow.component.combobox.ComboBox; | |||||
| import com.vaadin.flow.component.html.Div; | |||||
| import com.vaadin.flow.component.html.H2; | |||||
| import com.vaadin.flow.component.orderedlayout.HorizontalLayout; | |||||
| import com.vaadin.flow.component.orderedlayout.VerticalLayout; | |||||
| import com.vaadin.flow.component.radiobutton.RadioButtonGroup; | |||||
| import com.vaadin.flow.data.renderer.ComponentRenderer; | |||||
| import com.vaadin.flow.router.PageTitle; | |||||
| import com.vaadin.flow.router.Route; | |||||
| import com.vaadin.flow.server.VaadinSession; | |||||
| import jakarta.annotation.security.PermitAll; | |||||
| import java.util.List; | |||||
| @PermitAll | |||||
| @PageTitle("Configuración del sistema") | |||||
| @Route(value = "configuracion", layout = MainLayout.class) | |||||
| public class ConfiguracionView extends VerticalLayout { | |||||
| private static final String THEME_SESSION_KEY = "userTheme"; | |||||
| private static final String THEME_LOCALSTORAGE_KEY = "appThemePreference"; | |||||
| private final List<ColorOptions> primaryTextColorOptions = List.of( | |||||
| new ColorOptions("Color 1", "#A02142"), | |||||
| new ColorOptions("Color 2", "#691B31"), | |||||
| new ColorOptions("Color 3", "#BC955B"), | |||||
| new ColorOptions("Color 4", "#DDC9A3"), | |||||
| new ColorOptions("Color 5", "#6F7271") | |||||
| ); | |||||
| private final List<ColorOptions> primaryColorOptions = List.of( | |||||
| new ColorOptions("Color 1", "#A02142"), | |||||
| new ColorOptions("Color 2", "#691B31"), | |||||
| new ColorOptions("Color 3", "#BC955B"), | |||||
| new ColorOptions("Color 4", "#DDC9A3"), | |||||
| new ColorOptions("Color 5", "#6F7271") | |||||
| ); | |||||
| public ConfiguracionView() { | |||||
| setSpacing(true); | |||||
| loadCustomVariablesOnStartup(); | |||||
| // Selector de modo claro u obscuro | |||||
| RadioButtonGroup<String> themeSelector = createThemeSelector(); | |||||
| // Selector de colores de texto primarios | |||||
| ComboBox<ColorOptions> primaryTextColorSelector = createPrimaryTextColorComboBox(); | |||||
| primaryTextColorSelector.setWidth("250px"); | |||||
| // Selector del color primario | |||||
| ComboBox<ColorOptions> primaryColorSelector = createPrimaryColorComboBox(); | |||||
| primaryColorSelector.setWidth("250px"); | |||||
| HorizontalLayout layout1 = new HorizontalLayout(themeSelector); | |||||
| layout1.setWidthFull(); | |||||
| HorizontalLayout layout2 = new HorizontalLayout(primaryColorSelector, primaryTextColorSelector); | |||||
| layout2.setWidthFull(); | |||||
| VerticalLayout layoutPadre = new VerticalLayout(layout1, layout2); | |||||
| layoutPadre.getStyle() | |||||
| .set("border-radius", "10px") | |||||
| .set("box-shadow", "0 4px 8px rgba(0, 0, 0, 0.2)"); | |||||
| add(layoutPadre); | |||||
| } | |||||
| private void applyTheme(String themeAttribute) { | |||||
| String js = """ | |||||
| if ('%s' === 'dark') { | |||||
| document.documentElement.setAttribute('theme', 'dark'); | |||||
| localStorage.setItem('%s', 'dark'); | |||||
| } else { | |||||
| document.documentElement.removeAttribute('theme'); | |||||
| localStorage.setItem('%s', 'light'); | |||||
| } | |||||
| """.formatted( | |||||
| themeAttribute, | |||||
| THEME_LOCALSTORAGE_KEY, | |||||
| THEME_LOCALSTORAGE_KEY | |||||
| ); | |||||
| UI.getCurrent().getPage().executeJs(js); | |||||
| VaadinSession.getCurrent().setAttribute( | |||||
| THEME_SESSION_KEY, | |||||
| themeAttribute.isEmpty() ? "light" : "dark" | |||||
| ); | |||||
| } | |||||
| private ComboBox<ColorOptions> createPrimaryColorComboBox() { | |||||
| ComboBox<ColorOptions> selector = new ComboBox<>("Color Primario"); | |||||
| selector.setItems(primaryColorOptions); | |||||
| selector.setClearButtonVisible(false); | |||||
| selector.setAllowCustomValue(false); | |||||
| final String cssVariable = "--lumo-primary-color"; | |||||
| UI.getCurrent().getPage().executeJs(String.format("return localStorage.getItem('config:%s');", cssVariable)) | |||||
| .then(String.class, savedHex -> { | |||||
| ColorOptions savedOption = primaryTextColorOptions.stream() | |||||
| .filter(opt -> opt.getHexValue().equalsIgnoreCase(savedHex)) | |||||
| .findFirst() | |||||
| .orElse(primaryTextColorOptions.get(0)); | |||||
| selector.setValue(savedOption); | |||||
| }); | |||||
| selector.addValueChangeListener(event -> { | |||||
| ColorOptions selectedOption = event.getValue(); | |||||
| if (selectedOption != null) { | |||||
| applyCssVariable(cssVariable, selectedOption.getHexValue()); | |||||
| } | |||||
| }); | |||||
| selector.setRenderer(new ComponentRenderer<>(colorOptions -> { | |||||
| Div div = new Div(); | |||||
| div.setText(colorOptions.getName()); | |||||
| div.getStyle().set("display", "flex") | |||||
| .set("align-items", "center"); | |||||
| Div colorSwatch = new Div(); | |||||
| colorSwatch.getStyle().set("background-color", colorOptions.getHexValue()) | |||||
| .set("width", "16px") | |||||
| .set("height", "16px") | |||||
| .set("border-radius", "50%") | |||||
| .set("margin-right", "8px") | |||||
| .set("border", "1px solid var(--lumo-border-color)"); | |||||
| div.addComponentAsFirst(colorSwatch); | |||||
| return div; | |||||
| })); | |||||
| return selector; | |||||
| } | |||||
| private ComboBox<ColorOptions> createPrimaryTextColorComboBox() { | |||||
| ComboBox<ColorOptions> selector = new ComboBox<>("Color de los textos"); | |||||
| selector.setItems(primaryTextColorOptions); | |||||
| selector.setClearButtonVisible(false); | |||||
| selector.setAllowCustomValue(false); | |||||
| final String cssVariable = "--lumo-primary-text-color"; | |||||
| UI.getCurrent().getPage().executeJs(String.format("return localStorage.getItem('config:%s');", cssVariable)) | |||||
| .then(String.class, savedHex -> { | |||||
| ColorOptions savedOption = primaryTextColorOptions.stream() | |||||
| .filter(opt -> opt.getHexValue().equalsIgnoreCase(savedHex)) | |||||
| .findFirst() | |||||
| .orElse(primaryTextColorOptions.get(0)); | |||||
| selector.setValue(savedOption); | |||||
| }); | |||||
| selector.addValueChangeListener(event -> { | |||||
| ColorOptions selectorOption = event.getValue(); | |||||
| if (selectorOption != null) { | |||||
| applyCssVariable(cssVariable, selectorOption.getHexValue()); | |||||
| } | |||||
| }); | |||||
| selector.setRenderer(new ComponentRenderer<>(colorOptions -> { | |||||
| Div div = new Div(); | |||||
| div.setText(colorOptions.getName()); | |||||
| div.getStyle().set("display", "flex") | |||||
| .set("align-items", "center"); | |||||
| Div colorSwatch = new Div(); | |||||
| colorSwatch.getStyle().set("background-color", colorOptions.getHexValue()) | |||||
| .set("width","15px") | |||||
| .set("height","15px") | |||||
| .set("border-radius","50%") | |||||
| .set("margin-right","10px") | |||||
| .set("border","1px solid var(--lumo-border-color)"); | |||||
| div.addComponentAsFirst(colorSwatch); | |||||
| return div; | |||||
| })); | |||||
| return selector; | |||||
| } | |||||
| private RadioButtonGroup<String> createThemeSelector() { | |||||
| RadioButtonGroup<String> themeSelector = new RadioButtonGroup<>("Modo de interfaz (Tema)"); | |||||
| themeSelector.setItems("Claro (predeterminado)", "Oscuro"); | |||||
| String currentTheme = (String) VaadinSession.getCurrent().getAttribute(THEME_SESSION_KEY); | |||||
| themeSelector.setValue( | |||||
| "dark".equals(currentTheme) | |||||
| ? "Oscuro" | |||||
| : "Claro (predeterminado)" | |||||
| ); | |||||
| themeSelector.addValueChangeListener(e -> { | |||||
| String selection = e.getValue(); | |||||
| String themeAttribute = selection.contains("Oscuro") ? "dark" : ""; | |||||
| applyTheme(themeAttribute); | |||||
| }); | |||||
| return themeSelector; | |||||
| } | |||||
| public static class ColorOptions { | |||||
| private final String name; | |||||
| private final String hexValue; | |||||
| public ColorOptions(String name, String hexValue) { | |||||
| this.name = name; | |||||
| this.hexValue = hexValue; | |||||
| } | |||||
| public String getName() { return name; } | |||||
| public String getHexValue() { return hexValue; } | |||||
| @Override | |||||
| public String toString() { return name; } | |||||
| } | |||||
| private void applyCssVariable(String variableName, String colorValue) { | |||||
| String jsSetVar = String.format( | |||||
| "document.documentElement.style.setProperty('%s', '%s');", | |||||
| variableName, | |||||
| colorValue | |||||
| ); | |||||
| String jsSetStorage = String.format( | |||||
| "localStorage.setItem('config:%s', '%s');", | |||||
| variableName, | |||||
| colorValue | |||||
| ); | |||||
| UI.getCurrent().getPage().executeJs(jsSetVar, jsSetStorage); | |||||
| VaadinSession.getCurrent().setAttribute(variableName, colorValue); | |||||
| } | |||||
| private void loadCustomVariablesOnStartup() { | |||||
| final String[] customVariables = { | |||||
| "--lumo-primary-text-color", | |||||
| "--lumo-primary-color" | |||||
| }; | |||||
| StringBuilder jsLoadScript = new StringBuilder(); | |||||
| for (String cssVariable : customVariables) { | |||||
| String storageKey = "config:" + cssVariable; | |||||
| jsLoadScript.append(String.format( | |||||
| "const saved_%1$s = localStorage.getItem('%2$s');" + | |||||
| "if (saved_%1$s) { document.documentElement.style.setProperty('%3$s', saved_%1$s); }", | |||||
| cssVariable.replace("-", "_"), | |||||
| storageKey, | |||||
| cssVariable | |||||
| )); | |||||
| } | |||||
| UI.getCurrent().getPage().executeJs(jsLoadScript.toString()); | |||||
| } | |||||
| } | |||||