| @ -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()); | |||
| } | |||
| } | |||