001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2017-2018 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jgrapes.webconsole.base.events; 020 021import java.io.IOException; 022import java.io.Writer; 023import java.net.URI; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.List; 028import java.util.Locale; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.stream.Collectors; 032import org.jgrapes.webconsole.base.Conlet.RenderMode; 033import org.jgrapes.webconsole.base.RenderSupport; 034import org.jgrapes.webconsole.base.events.AddPageResources.ScriptResource; 035 036/** 037 * Adds a web console component type with its global resources 038 * (JavaScript and/or CSS) to the console page. Specifying global 039 * resources result in the respective 040 * `<link .../>` or `<script ...></script>` nodes 041 * being added to the page's `<head>` node. 042 * 043 * This in turn causes the browser to issue `GET` requests that 044 * (usually) refer to the web console component's resources. These requests are 045 * converted to {@link ConletResourceRequest}s by the web console and 046 * sent to the web console components, which must respond to the requests. 047 * 048 * The sequence of events is shown in the diagram. 049 * 050 * ![WebConsole Ready Event Sequence](AddConletTypeSeq.svg) 051 * 052 * See {@link ResourceRequest} for details about the processing 053 * of the {@link ConletResourceRequest}. 054 * 055 * A conlet's JavaScript may (and probably must) make use of 056 * the functions provided by the web console page. See the 057 * <a href="../jsdoc/classes/Console.html">JavaScript 058 * documentation of these functions</a> for details. 059 * 060 * @startuml AddConletTypeSeq.svg 061 * hide footbox 062 * 063 * activate Browser 064 * Browser -> WebConsole: "consoleReady" 065 * deactivate Browser 066 * activate WebConsole 067 * WebConsole -> ConletX: ConsoleReady 068 * deactivate WebConsole 069 * activate ConletX 070 * ConletX -> WebConsole: AddConletType 071 * deactivate ConletX 072 * activate WebConsole 073 * WebConsole -> Browser: "addConletType" 074 * activate Browser 075 * deactivate WebConsole 076 * Browser -> WebConsole: "GET <conlet resource URI>" 077 * activate WebConsole 078 * WebConsole -> ConletX: ConletResourceRequest 079 * deactivate Browser 080 * activate ConletX 081 * deactivate ConletX 082 * 083 * @enduml 084 */ 085public class AddConletType extends ConsoleCommand { 086 087 private final String conletType; 088 private Map<Locale, String> displayNames = Collections.emptyMap(); 089 private final List<URI> cssUris = new ArrayList<>(); 090 private final List<ScriptResource> scriptResources = new ArrayList<>(); 091 private List<RenderMode> renderModes; 092 private final List<PageComponentSpecification> pageComponents 093 = new ArrayList<>(); 094 095 /** 096 * Create a new event for the given web console component type. 097 * 098 * @param conletType a unique id for the web console component type 099 * (usually the class name) 100 */ 101 public AddConletType(String conletType) { 102 this.conletType = conletType; 103 } 104 105 /** 106 * Return the web console component type. 107 * 108 * @return the web console component type 109 */ 110 public String conletType() { 111 return conletType; 112 } 113 114 /** 115 * Sets the names (by locale) used to display the type 116 * in the user interface. 117 * 118 * @param displayNames the display names 119 * @return the event for easy chaining 120 */ 121 @SuppressWarnings("PMD.LinguisticNaming") 122 public AddConletType setDisplayNames(Map<Locale, String> displayNames) { 123 this.displayNames = displayNames; 124 return this; 125 } 126 127 /** 128 * Return the display names. 129 * 130 * @return the displayNames 131 */ 132 public Map<Locale, String> displayNames() { 133 return displayNames; 134 } 135 136 /** 137 * Add a render mode to be offered to the user for creating 138 * new conlet instances. Several modes may be added. 139 * Usually only the modes {@link RenderMode#Preview} and 140 * {@link RenderMode#View} make sense and are the only ones 141 * supported by webconsoles. They commonly cause the conlet 142 * type to be added to a menu which is made available to the 143 * user. 144 * 145 * @param mode the mode 146 * @return the event for easy chaining 147 */ 148 public AddConletType addRenderMode(RenderMode mode) { 149 if (renderModes == null) { 150 renderModes = new ArrayList<>(); 151 } 152 renderModes.add(mode); 153 return this; 154 } 155 156 /** 157 * Return the render modes. 158 * 159 * @return the result 160 */ 161 public List<RenderMode> renderModes() { 162 if (renderModes == null) { 163 return Collections.emptyList(); 164 } 165 return renderModes; 166 } 167 168 /** 169 * Add a script resource to be requested by the browser. 170 * 171 * @param scriptResource the script resource 172 * @return the event for easy chaining 173 */ 174 public AddConletType addScript(ScriptResource scriptResource) { 175 scriptResources.add(scriptResource); 176 return this; 177 } 178 179 /** 180 * Add the URI of a CSS resource that is to be added to the 181 * header section of the web console page. 182 * 183 * @param renderSupport the render support for mapping the `uri` 184 * @param uri the URI 185 * @return the event for easy chaining 186 */ 187 public AddConletType addCss(RenderSupport renderSupport, URI uri) { 188 cssUris.add(renderSupport.conletResource(conletType(), uri)); 189 return this; 190 } 191 192 /** 193 * Return all script resources. 194 * 195 * @return the result 196 */ 197 public ScriptResource[] scriptResources() { 198 return scriptResources.toArray(new ScriptResource[0]); 199 } 200 201 /** 202 * Return all CSS URIs. 203 * 204 * @return the result 205 */ 206 public URI[] cssUris() { 207 return cssUris.toArray(new URI[0]); 208 } 209 210 /** 211 * Causes a container with this conlet's type as attribute 212 * "data-conlet-type" and classes "conlet conlet-content" 213 * to be added to the specified page area. The properties 214 * are added to the container as additional "data-conlet-..." 215 * attributes and will be passed to the {@link AddConletRequest} 216 * issued by the console when requesting the conlet's representation. 217 * 218 * Currently, the only defined page area is "headerIcons". 219 * When adding conlets in this area, the numeric property 220 * "priority" may be used to determine the order. The default 221 * value is 0. Conlets with the same priority are ordered 222 * by their type name. 223 * 224 * @param area the area into which the component is to be added 225 * @param properties the properties 226 * @return the event for easy chaining 227 * @see RenderMode#Content 228 */ 229 public AddConletType addPageContent(String area, 230 Map<String, String> properties) { 231 pageComponents.add(new PageComponentSpecification(area, properties)); 232 return this; 233 } 234 235 /** 236 * Return the list of page components. 237 * 238 * @return the list 239 */ 240 public List<PageComponentSpecification> pageContent() { 241 return pageComponents; 242 } 243 244 @Override 245 public void emitJson(Writer writer) throws IOException { 246 emitJson(writer, "addConletType", conletType(), 247 displayNames().entrySet().stream() 248 .collect(Collectors.toMap(e -> e.getKey().toLanguageTag(), 249 Entry::getValue)), 250 Arrays.stream(cssUris()).map(URI::toString).toArray(String[]::new), 251 scriptResources(), renderModes().stream().map(RenderMode::name) 252 .toArray(size -> new String[size]), 253 pageComponents); 254 } 255 256 /** 257 * Specifies an embedded instance to be added. 258 */ 259 public static class PageComponentSpecification { 260 private final String area; 261 private final Map<String, String> properties; 262 263 /** 264 * Instantiates a new embed spec. 265 * 266 * @param area the area 267 * @param properties the properties 268 */ 269 public PageComponentSpecification(String area, 270 Map<String, String> properties) { 271 super(); 272 this.area = area; 273 this.properties = properties; 274 } 275 276 /** 277 * Gets the area. 278 * 279 * @return the area 280 */ 281 public String getArea() { 282 return area; 283 } 284 285 /** 286 * Gets the properties. 287 * 288 * @return the properties 289 */ 290 public Map<String, String> getProperties() { 291 return properties; 292 } 293 } 294 295}