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}