Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flow initial pwa #4334

Merged
merged 50 commits into from
Jul 11, 2018
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
74ccbf0
Adds automatic PWA -initialization for all Flow-projects.
mikotin Jun 19, 2018
110a643
Renamed Manifest to PWA.
mikotin Jun 25, 2018
b03bdb0
Added missing type from icons
mikotin Jun 26, 2018
1953c59
Added missing href setting
mikotin Jun 26, 2018
3538cbf
Changed pwa handlers to a single handler with pre-calculated responses.
mikotin Jun 26, 2018
8908c3b
Fixed icon inject for offline page.
mikotin Jun 26, 2018
fc2a561
Updated pwa properties to better support add to home screen -function…
mikotin Jun 26, 2018
f52538a
Cleared up the code and added comments.
mikotin Jun 26, 2018
5b0b1cc
Added support for custom offline resources.
mikotin Jun 26, 2018
10c19f1
Added missing documentation, moved PWARegistry to server -package.
mikotin Jun 27, 2018
97d580c
Added required docs and fixed minor issues reported by bot.
mikotin Jun 27, 2018
efeb58e
Added meta tags for better PWA support.
mikotin Jun 27, 2018
e19249a
Fixed commented issues.
mikotin Jun 28, 2018
b942b51
Fixed comments.
mikotin Jun 28, 2018
45f9931
Fixed comments.
mikotin Jun 28, 2018
96451c3
Refactored Icon class and added missing copyright headers.
mikotin Jun 29, 2018
11454ad
Moved PWA scanning to route registry initializer.
mikotin Jun 29, 2018
3c4a794
Fixed reported issues
mikotin Jun 29, 2018
e0ab2da
Changed general exception to a named exception.
mikotin Jul 1, 2018
067fb12
Removed unused import.
mikotin Jul 1, 2018
2185806
Replaced repeated strings with constants.
mikotin Jul 1, 2018
a5ab9fe
Minor code style change. Added tesource stream close handling.
mikotin Jul 1, 2018
c8b77e2
Changed resource reading as informed by bot.
mikotin Jul 1, 2018
a73ebe4
Added missing copyright header.
mikotin Jul 2, 2018
9a44794
Added tests for PWA annotation.
mikotin Jul 4, 2018
f7ad12c
Changed default logo for pwa
mikotin Jul 4, 2018
d5d926f
Default display changed to standalone and updated default resources.
mikotin Jul 5, 2018
ec3e500
Fixed theme-color meta tag.
mikotin Jul 5, 2018
eb57b39
Fixed pwa meta tags and tests for those.
mikotin Jul 5, 2018
3a74bd5
Changed the copyright year to 2018. (Came back to the future)
mikotin Jul 5, 2018
d8adc3f
Changed the copyright year and added constant for repeated string.
mikotin Jul 5, 2018
ca2d2f4
Merge branch 'master' into flow-initial-pwa
Jul 5, 2018
6c4b20a
Styling fixes. (#4365)
SomeoneToIgnore Jul 5, 2018
15a789f
Remove unneeded CSS property.
Jul 5, 2018
663cd54
Added local serving of needed Workbox -files.
mikotin Jul 6, 2018
c71e058
Merge branch 'flow-initial-pwa' of ssh://github.com/vaadin/flow into …
mikotin Jul 6, 2018
682c3ad
Removed enabled from PWA annotation.
mikotin Jul 6, 2018
0d67035
Cleared silly issues that were left in the code.
mikotin Jul 6, 2018
efb7050
Moved fixed value out of if clause.
mikotin Jul 6, 2018
b2bd0cd
Replaced getCurrent() with proper way.
mikotin Jul 6, 2018
3612a12
Added synchronized for to lazy initializator.
mikotin Jul 6, 2018
38c0460
Removed unused import.
mikotin Jul 6, 2018
e1fe75d
Changed private field to AtomitRefence.
mikotin Jul 6, 2018
b4c69d0
Updated version.
mikotin Jul 6, 2018
ff85742
Fixed constant.
mikotin Jul 6, 2018
34eeaa7
Merge branch 'master' into flow-initial-pwa
Jul 10, 2018
6af74cd
Fix sonar issues.
Jul 10, 2018
4da44a9
Code review fixes.
Jul 10, 2018
689730a
Fix NPE.
Jul 10, 2018
a55390a
Replace string concatenation with append.
Jul 11, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
321 changes: 321 additions & 0 deletions flow-server/src/main/java/com/vaadin/flow/dom/Icon.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
package com.vaadin.flow.dom;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.jsoup.nodes.Element;

/**
* Implementation of icon -element.
*
* Creates the href automatically based on
* - baseName (the file name with path, as "icons/icon.png"
* - width (width of icon)
* - height (height of icon)
* - (possibly) fileHash (the hashcode of image file)
*
* The href will be set as:
* [basename]-[width]x[height].png{?[filehash]}
*
* The trailing ?[filehash] will be added if icon cache is not controlled
* by service worker: cached = false
*
* So caching of a icon is left left for browser if it's not cached with
* service worker.
*
*/
public class Icon implements Serializable {
/**
* Where icon belongs to.
*
* In header or manifest.json.
*
*/
public enum Domain {
HEADER,
MANIFEST;
}

private boolean cached = false;
private int width = 0;
private int height = 0;
private long fileHash = 0;
private String baseName = "icons/icon.png";
private Domain domain = Domain.HEADER;
private boolean hrefOverride = false;
private byte[] data;

private Map<String, String> attributes = new HashMap<>();
private String tag = "link";

public Icon() {
attr("type", "image/png");
rel("icon");
}

/**
* Chaining setter for size.
*
* @param width width of icon
* @param height height of icon
* @return self
*/
public Icon size(int width, int height) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setSize instead of size

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aye, there are chaining setters used in Icon with just property name for each did on purpose. Using setFoo with chaining seems not so good. I'll change those of course if needed. For instance to withFoo if it would be better? Or remove chaining totally, if it doesn't suite.

this.width = width;
this.height = height;

attr("sizes", width + "x" + height);
setRelativeName();
return this;
}

/**
* Gets an {@link Element} presentation of the icon.
*
* @return an {@link Element} presentation of the icon
*/
public Element asElement() {
Element element = new Element(tag);
attributes.entrySet().forEach(entry -> {
element.attr(entry.getKey(), entry.getValue());
});
return element;
}

private Icon attr(String key, String value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For readability of code. addAttribute

attributes.put(key, value);
return this;
}

private String attr(String key) {
return attributes.get(key);
}

/**
* Width of icon.
*
* @return Width of icon
*/
public int getWidth() {
return width;
}

/**
* Height of icon.
*
* @return Height of icon
*/
public int getHeight() {
return height;
}

/**
* Should the icon be cached viá Service Worker.
*
* @return Should the icon be cached viá Service Worker.
*/
public boolean cached() {
return cached;
}

/**
* Chained setter for chained.
*
* @param cached Should the icon cached viá Service Worker
* @return
*/
public Icon cached(boolean cached) {
this.cached = cached;
return this;
}

/**
* Sets the href based on icon values.
*
*/
private void setRelativeName() {
if (!hrefOverride) {
int split = baseName.lastIndexOf(".");
String link = baseName.substring(0,split) + "-" + sizes() +
baseName.substring(split);
if (!cached) {
link = link + "?" + fileHash;
}
attr("href", link);
}
}

/**
* Gets string for sizes -attribute.
*
*
* @return a String as [size]x[size]
*/
public String sizes() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getSize

return attr("sizes");
}

/**
* Chaining setter of href -attribute.
*
* Href is forced as relative, so all [./] -chars are removed from the
* start of the href.
*
* Href is always set when either size or basename is set.
*
* @param href href
* @return self
*/
public Icon href(String href) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setHref

hrefOverride = true;
attr("href", href.replaceAll("^[\\./]+", ""));
return this;
}

/**
* href attribute.
*
* @return href attribute
*/
public String href() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getHref

return attr("href");
}

/**
* Return href with '/' -prefix and removed possible ?[fileHash].
*
* Used in matching, when serving images.
*
* @return Return href with '/' -prefix and removed possible ?[fileHash]
*/
public String relHref() {
String[] splitted = href().split("\\?");
return "/" + splitted[0];
}

/**'
* Gets the cache-string used in Google Workbox caching.
*
* @return "{ url: '[href]', revision: '[fileHash' }"
*/
public String cache() {
return String.format("{ url: '%s', revision: '%s' }", href(),
fileHash);
}

/**
* Getter for rel attribute.
*
* @return rel attribute
*/
public String rel() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getRel

return attr("rel");
}

/**
* Chaining setter for rel-attribute.
*
* @param rel rel value
* @return self
*/
public Icon rel(String rel) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setRel

attr("rel", rel);
return this;
}

/**
* Type attribute.
*
* @return type -attribute
*/
public String type() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getType

return attr("type");
}

/**
* Chaining setter for domain.
*
* @param domain Domain
* @return self
*/
public Icon domain(Domain domain) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setDomain

this.domain = domain;
return this;
}

/**
* Domain of icon.
*
* @return Domain of icon
*/
public Domain domain() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDomain

return this.domain;
}

/**
* Media attribute.
*
* @return Media attribute
*/
public String media() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMedia

return attr("media");
}

/**
* Chaining setter for media attribute.
*
* @param media media
* @return self
*/
public Icon media(String media) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setMedia

attr("media", media);
return this;
}

/**
* Chaining setter of basename.
*
* @param baseName image full name with path, like "icon/icon.png"
* @return self
*/
public Icon baseName(String baseName) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setBaseName

this.baseName = baseName;
setRelativeName();
return this;
}

/**
* Sets the image presenting the icon.
*
* @param image image in png -format
* @throws IOException
*/
public void setImage(BufferedImage image) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write( image, "png", baos );
baos.flush();
data = baos.toByteArray();
fileHash = Arrays.hashCode(data);
setRelativeName();
baos.close();
}

/**
* Writes the image to output stream.
*
* @param outputStream output stream
* @throws IOException
*/
public synchronized void write(OutputStream outputStream)
throws IOException {
outputStream.write(data);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import com.vaadin.flow.component.page.Inline;
import com.vaadin.flow.component.page.Push;
import com.vaadin.flow.component.page.Viewport;
import com.vaadin.flow.dom.Icon;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.internal.AnnotationReader;
import com.vaadin.flow.internal.ReflectTools;
Expand Down Expand Up @@ -570,6 +571,7 @@ private static void writeBootstrapPage(VaadinResponse response, String html)
private static List<Element> setupDocumentHead(Element head,
BootstrapContext context) {
setupMetaAndTitle(head, context);
setupPWA(head);
setupCss(head, context);

JsonObject initialUIDL = getInitialUidl(context.getUI());
Expand Down Expand Up @@ -701,6 +703,42 @@ private static void setupMetaAndTitle(Element head,
});
}

private static void setupPWA(Element head) {
PWARegistry registry = VaadinService.getCurrent().getPwaRegistry();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VaadinService.getCurrent()
Please don't do this.
Artur made an effort to avoid getCurrent() methods call whenever it's possible.

Please pass BootstrapContext context as a method parameter and use it to get VaadinSession and a service from it. There are examples in the methods of this class.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done by Mikko.


PwaConfiguration config = registry.getPwaConfiguration();

if (config.isEnabled()) {
// Describe PWA capability for IOS devices
head.appendElement(META_TAG)
.attr("name", "apple-mobile-web-app-capable")
.attr(CONTENT_ATTRIBUTE, "yes");

// Theme color
head.appendElement(META_TAG)
.attr("theme-color", config.getThemeColor());
head.appendElement(META_TAG)
.attr("apple-mobile-web-app-status-bar-style",
config.getThemeColor());

// Add manifest
head.appendElement("link").attr("rel", "manifest")
.attr("href", config.getManifestPath());

// Add icons
for (Icon icon : registry.getHeaderIcons()) {
head.appendChild(icon.asElement());
}

// Add service worker initialization
head.appendElement("script").text("if ('serviceWorker' in navigator) {\n"
+ " window.addEventListener('load', function() {\n"
+ " navigator.serviceWorker.register('" +
config.getServiceWorkerPath() + "');\n"
+ " });\n" + "}");
}
}

private static void appendWebComponentsPolyfills(Element head,
BootstrapContext context) {
VaadinSession session = context.getSession();
Expand Down