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