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.stream.Collectors;
031import org.jdrupes.json.JsonArray;
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 toJson(Writer writer) throws IOException {
246        JsonArray strArray = JsonArray.create();
247        for (ScriptResource scriptResource : scriptResources()) {
248            strArray.append(scriptResource.toJsonValue());
249        }
250        toJson(writer, "addConletType", conletType(),
251            displayNames().entrySet().stream()
252                .collect(Collectors.toMap(e -> e.getKey().toLanguageTag(),
253                    e -> e.getValue())),
254            Arrays.stream(cssUris()).map(
255                uri -> 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}