1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.dialog;
35
36 import static com.vaadin.server.Sizeable.Unit.PERCENTAGE;
37 import static java.util.stream.Collectors.toMap;
38
39 import info.magnolia.objectfactory.ComponentProvider;
40 import info.magnolia.ui.CloseHandler;
41 import info.magnolia.ui.DialogBuilder;
42 import info.magnolia.ui.api.action.AbstractActionExecutor;
43 import info.magnolia.ui.api.action.ActionDefinition;
44 import info.magnolia.ui.api.action.ActionExecutionException;
45 import info.magnolia.ui.api.availability.AvailabilityChecker;
46 import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
47 import info.magnolia.ui.dialog.layout.DefaultEditorActionLayoutProducer;
48 import info.magnolia.ui.form.LocaleContext;
49 import info.magnolia.ui.framework.databinding.view.EditorView;
50 import info.magnolia.ui.framework.layout.LayoutDefinition;
51 import info.magnolia.ui.framework.layout.LayoutProducer;
52
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.HashMap;
56 import java.util.List;
57 import java.util.Locale;
58 import java.util.Map;
59 import java.util.Optional;
60 import java.util.function.Supplier;
61
62 import com.vaadin.event.ShortcutAction;
63 import com.vaadin.event.ShortcutListener;
64 import com.vaadin.server.Responsive;
65 import com.vaadin.ui.Button;
66 import com.vaadin.ui.ComboBox;
67 import com.vaadin.ui.Component;
68 import com.vaadin.ui.Window;
69
70 import lombok.Builder;
71 import lombok.NonNull;
72
73
74
75
76 public class DialogHelper {
77
78 private EditorView editorView;
79 private Component content;
80 private ComponentProvider componentProvider;
81 private LocaleContext localeContext;
82 private Supplier<Locale> userLanguage = Locale::getDefault;
83 private Map<String, ActionDefinition> actions;
84 private List<Object> items;
85 private String caption;
86 private LayoutDefinition layoutDefinition;
87
88 private DialogComponent dialogComponent;
89
90 public DialogHelper withContent(EditorView editorView) {
91 this.editorView = editorView;
92 return withContent(editorView.asVaadinComponent())
93 .withComponentProvider(editorView.getComponentProvider());
94 }
95
96 public DialogHelper withContent(Component content) {
97 this.content = content;
98 return this;
99 }
100
101 public DialogHelper withComponentProvider(ComponentProvider componentProvider) {
102 this.componentProvider = componentProvider;
103 return this;
104 }
105
106 public DialogHelper withUserLanguage(Supplier<Locale> userLanguage) {
107 this.userLanguage = userLanguage;
108 return this;
109 }
110
111 public DialogHelper withLocaleContext(LocaleContext localeContext) {
112 this.localeContext = localeContext;
113 return this;
114 }
115
116 public DialogHelper withActions(Map<String, ActionDefinition> actions) {
117 this.actions = actions;
118 return this;
119 }
120
121 public DialogHelper withItems(List<Object> items) {
122 this.items = items;
123 return this;
124 }
125
126 public DialogHelper withCaption(String caption) {
127 this.caption = caption;
128 return this;
129 }
130
131 public DialogHelper withLayoutDefinition(LayoutDefinition layoutDefinition) {
132 this.layoutDefinition = layoutDefinition;
133 return this;
134 }
135
136 public Component createDialogComponent() {
137 dialogComponent = getBuilder().build();
138 addKeyboardShortcuts();
139 return dialogComponent;
140 }
141
142 public void buildAndOpen() {
143 Window window = getBuilder().buildAndOpen();
144 dialogComponent = (DialogComponent) window.getContent();
145 addKeyboardShortcuts();
146 window.addStyleNames("dialog");
147 window.setWidth(95, PERCENTAGE);
148 window.setDraggable(false);
149 window.setResizable(true);
150 Responsive.makeResponsive(window);
151 Optional.ofNullable(editorView).ifPresent(editorView -> editorView.bindInstance(CloseHandler.class, window::close));
152 }
153
154 private Optional<LayoutProducer> getLayoutProducer() {
155 return Optional.ofNullable(layoutDefinition)
156 .map(layoutDefinition -> componentProvider.newInstance(DefaultEditorActionLayoutProducer.class));
157 }
158
159 private void executeAction(ActionDefinition actionDefinition, Object... args) {
160 try {
161 DialogActionExecutor.builder()
162 .componentProvider(componentProvider)
163 .definition(actionDefinition)
164 .build()
165 .execute(actionDefinition.getName(), args);
166 } catch (ActionExecutionException e) {
167 throw new RuntimeException(e);
168 }
169 }
170
171 private void addKeyboardShortcuts() {
172 dialogComponent.addShortcutListener(new ShortcutListener("commit", ShortcutAction.KeyCode.ENTER, new int[]{}) {
173 @Override
174 public void handleAction(Object sender, Object target) {
175 Optional.ofNullable(DialogHelper.this.actions.get("commit"))
176 .filter(DialogHelper.this::isAvailable)
177 .ifPresent(actionDefinition -> executeAction(actionDefinition));
178 }
179 });
180 dialogComponent.addShortcutListener(new ShortcutListener("cancel", ShortcutAction.KeyCode.ESCAPE, new int[]{}) {
181 @Override
182 public void handleAction(Object sender, Object target) {
183 Optional.ofNullable(DialogHelper.this.actions.get("cancel")).ifPresent(actionDefinition -> executeAction(actionDefinition));
184 }
185 });
186 }
187
188 private DialogBuilder getBuilder() {
189 Map<String, Component> actions = createActions(this.actions);
190
191 Optional.ofNullable(this.localeContext)
192 .ifPresent(context -> actions.put("localeSelector", createLocaleSelector()));
193
194 return DialogBuilder.dialog()
195 .modal()
196 .withTitle(caption)
197 .withContent(content)
198 .withActions(new ArrayList<>(actions.values()))
199 .withFooter(getLayoutProducer()
200 .map(layoutProducer -> layoutProducer.createLayout(layoutDefinition, actions))
201 .orElse(null)
202 );
203 }
204
205 private <T> Map<String, Component> createActions(Map<String, ActionDefinition> actions) {
206 return actions == null ? new HashMap<>() : actions.entrySet().stream()
207 .collect(toMap(Map.Entry::getKey, entry -> {
208 Component component = new Button(entry.getValue().getName(), event -> executeAction(entry.getValue()));
209 component.addStyleNames(entry.getValue().getName());
210 if (!isAvailable(entry.getValue())) {
211 component.setEnabled(false);
212 }
213 return component;
214 }));
215 }
216
217 private Component createLocaleSelector() {
218
219 final ComboBox<Locale> locales = new ComboBox<>();
220
221 I18NAuthoringSupport i18NAuthoringSupport = componentProvider.getComponent(I18NAuthoringSupport.class);
222 List<Locale> availableLocales = i18NAuthoringSupport.getAvailableLocales();
223 if (availableLocales.isEmpty()) {
224 availableLocales = Collections.singletonList(i18NAuthoringSupport.getDefaultLocale());
225 }
226
227 locales.setItems(availableLocales);
228 locales.setItemCaptionGenerator(locale -> {
229
230 Locale preferredLocale = userLanguage.get();
231 String label = locale.getDisplayLanguage(preferredLocale);
232 if (!locale.getDisplayCountry(preferredLocale).isEmpty()) {
233 label += " (" + locale.getDisplayCountry(preferredLocale) + ")";
234 }
235 return label;
236 });
237 locales.setEmptySelectionAllowed(false);
238 locales.setTextInputAllowed(false);
239 locales.setSelectedItem(localeContext.current().value().orElse(i18NAuthoringSupport.getDefaultLocale()));
240 locales.addValueChangeListener(locale -> localeContext.current().set(locale.getValue()));
241
242 Optional.ofNullable(editorView).ifPresent(editorView ->
243 localeContext.current().observeNullable(locale -> Optional.ofNullable(dialogComponent)
244 .ifPresent(dialogComponent -> dialogComponent.setContent(editorView.getLayout(locale)))
245 ));
246
247 return locales;
248 }
249
250 private boolean isAvailable(ActionDefinition actionDefinition) {
251 return Optional.ofNullable(componentProvider)
252 .map(componentProvider -> componentProvider.getComponent(AvailabilityChecker.class))
253 .map(availabilityChecker -> availabilityChecker.isAvailable(actionDefinition.getAvailability(), items))
254 .orElse(true);
255 }
256
257 private static class DialogActionExecutor extends AbstractActionExecutor {
258
259 private final ActionDefinition definition;
260
261 @Builder
262 private DialogActionExecutor(@NonNull ComponentProvider componentProvider, @NonNull ActionDefinition definition) {
263 super(componentProvider);
264 this.definition = definition;
265 }
266
267 @Override
268 public ActionDefinition getActionDefinition(String actionName) {
269 return definition;
270 }
271 }
272
273 }