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

feat: Theme can be defined as string or class #9349

Merged
merged 18 commits into from
Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ yarn.lock

flow-client/src/main/resources/META-INF/resources/frontend/FlowClient.js
flow-tests/**/types.d.ts
flow-tests/test-themes/frontend/theme/app-theme/app-theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
* @since 1.0
*/
@Tag(Tag.DIV)
@Theme(Lumo.class)
@Theme(themeClass = Lumo.class)
@StyleSheet("frontend/src/css/demo.css")
@StyleSheet("frontend/src/css/prism.css")
public abstract class DemoView extends Component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public static class TranslatedImports extends Component {
}

@Route
@Theme(value = Lumo.class, variant = Lumo.DARK)
@Theme(themeClass = Lumo.class, variant = Lumo.DARK)
public static class MainView extends Component {
ButtonComponent buttonComponent;
IconComponent iconComponent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ private void assignThemeVariant() {
return;
}
AbstractTheme themeInstance = Instantiator.get(this)
.getOrCreate(theme.get().value());
.getOrCreate(theme.get().themeClass());
ThemeDefinition definition = new ThemeDefinition(theme.get());
Map<String, String> attributes = themeInstance
.getHtmlAttributes(definition.getVariant());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import com.vaadin.flow.component.WebComponentExporterFactory;
import com.vaadin.flow.server.frontend.scanner.ClassFinder;
import com.vaadin.flow.server.webcomponent.WebComponentModulesWriter;
import com.vaadin.flow.theme.Theme;
import com.vaadin.flow.theme.ThemeDefinition;

/**
* Generates embeddable web component files in npm mode, hiding the complexity
Expand Down Expand Up @@ -72,7 +74,8 @@ public FrontendWebComponentGenerator(ClassFinder finder) {
* @throws java.lang.IllegalStateException
* if {@code finder} cannot locate required classes
*/
public Set<File> generateWebComponents(File outputDirectory) {
public Set<File> generateWebComponents(File outputDirectory,
ThemeDefinition theme) {
pleku marked this conversation as resolved.
Show resolved Hide resolved
try {
final Class<?> writerClass = finder
.loadClass(WebComponentModulesWriter.class.getName());
Expand All @@ -81,9 +84,11 @@ public Set<File> generateWebComponents(File outputDirectory) {
.forEach(exporterRelatedClasses::add);
finder.getSubTypesOf(WebComponentExporterFactory.class.getName())
.forEach(exporterRelatedClasses::add);
final String themeName = theme == null ? "" : theme.getName();
return WebComponentModulesWriter.DirectoryWriter
.generateWebComponentsToDirectory(writerClass,
exporterRelatedClasses, outputDirectory, false);
exporterRelatedClasses, outputDirectory, false,
themeName);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(
"Unable to locate a required class using custom class "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -504,15 +504,15 @@ private NodeTasks(Builder builder) {
FrontendDependenciesScanner frontendDependencies = null;

if (builder.enablePackagesUpdate || builder.enableImportsUpdate) {
frontendDependencies = new FrontendDependenciesScanner.FrontendDependenciesScannerFactory()
.createScanner(!builder.useByteCodeScanner, classFinder,
builder.generateEmbeddableWebComponents);

if (builder.generateEmbeddableWebComponents) {
FrontendWebComponentGenerator generator = new FrontendWebComponentGenerator(
classFinder);
generator.generateWebComponents(builder.generatedFolder);
generator.generateWebComponents(builder.generatedFolder, frontendDependencies.getThemeDefinition());
}

frontendDependencies = new FrontendDependenciesScanner.FrontendDependenciesScannerFactory()
.createScanner(!builder.useByteCodeScanner, classFinder,
builder.generateEmbeddableWebComponents);
}

if (builder.createMissingPackageJson) {
Expand Down Expand Up @@ -578,6 +578,9 @@ private NodeTasks(Builder builder) {
builder.npmFolder, builder.generatedFolder,
builder.frontendDirectory, builder.tokenFile,
builder.tokenFileData, builder.enablePnpm));

commands.add(new TaskUpdateThemeImport(builder.npmFolder,
frontendDependencies.getThemeDefinition()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ static Map<String, String> getDefaultDevDependencies() {
defaults.put("lit-html", "1.2.1");
defaults.put("@types/validator", "10.11.3");
defaults.put("validator", "12.0.0");
defaults.put("construct-style-sheets-polyfill", "2.4.2");
pleku marked this conversation as resolved.
Show resolved Hide resolved

// Forcing chokidar version for now until new babel version is available
// check out https://github.com/babel/babel/issues/11488
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ protected Collection<String> getThemeLines() {
Collection<String> lines = new ArrayList<>();
AbstractTheme theme = getTheme();
ThemeDefinition themeDef = getThemeDefinition();

if (themeDef!= null && !themeDef.getName().equals("")) {
pleku marked this conversation as resolved.
Show resolved Hide resolved
// If we define a theme name we need to import theme/theme.js
lines.add("import {applyTheme} from 'theme/theme.js';");
lines.add("applyTheme(document);");
}

if (theme != null) {
if (!theme.getHeaderInlineContents().isEmpty()) {
lines.add("");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2000-2020 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.server.frontend;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import com.vaadin.flow.server.ExecutionFailedException;
import com.vaadin.flow.theme.ThemeDefinition;

import org.apache.commons.io.FileUtils;

/**
* Task for generating the theme.js file for importing application theme.
*
* @since
*/
public class TaskUpdateThemeImport implements FallibleCommand {

private File themeImportFile;
private ThemeDefinition theme;

TaskUpdateThemeImport(File npmFolder, ThemeDefinition theme) {
File nodeModules = new File(npmFolder, FrontendUtils.NODE_MODULES);
File flowFrontend = new File(nodeModules, FrontendUtils.FLOW_NPM_PACKAGE_NAME);
this.themeImportFile = new File(new File(flowFrontend, "theme"), "theme.js");
this.theme = theme;
}

@Override
public void execute() throws ExecutionFailedException {
if (theme == null || theme.getName().isEmpty()) {
return;
}
themeImportFile.getParentFile().mkdirs();

try {
FileUtils.write(
themeImportFile, "import {applyTheme as _applyTheme} from 'theme/" + theme.getName() + "/"
+ theme.getName() + ".js';\nexport const applyTheme = _applyTheme;\n",
Copy link
Contributor

Choose a reason for hiding this comment

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

As this file is generated, should we name it theme-generated.js instead ? As the users are not supposed to touch it themselves ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

StandardCharsets.UTF_8);
} catch (IOException e) {
throw new ExecutionFailedException("Unable to write theme import file", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
Expand Down Expand Up @@ -53,6 +54,7 @@ public class TaskUpdateWebpack implements FallibleCommand {
private final Path frontendDirectory;
private final boolean useV14Bootstrapping;
private final Path flowResourcesFolder;
private final Path resourceFolder;

/**
* Create an instance of the updater given all configurable parameters.
Expand Down Expand Up @@ -88,6 +90,7 @@ public class TaskUpdateWebpack implements FallibleCommand {
this.webpackConfigPath = webpackConfigFolder.toPath();
this.useV14Bootstrapping = useV14Bootstrapping;
this.flowResourcesFolder = flowResourcesFolder.toPath();
this.resourceFolder = new File(webpackOutputDirectory.getParentFile(), "resources").toPath();
}

@Override
Expand Down Expand Up @@ -138,7 +141,7 @@ private void createWebpackConfig() throws IOException {

private List<String> modifyWebpackConfig(File generatedFile)
throws IOException {
List<String> lines = FileUtils.readLines(generatedFile, "UTF-8");
List<String> lines = FileUtils.readLines(generatedFile, StandardCharsets.UTF_8);

String frontendLine = "const frontendFolder = require('path').resolve(__dirname, '"
+ getEscapedRelativeWebpackPath(frontendDirectory) + "');";
Expand All @@ -153,31 +156,38 @@ private List<String> modifyWebpackConfig(File generatedFile)
+ getEscapedRelativeWebpackPath(
flowResourcesFolder.resolve("VaadinDevmodeGizmo.js"))
+ "');";

String frontendFolder =
"const flowFrontendFolder = require('path').resolve(__dirname, '" + getEscapedRelativeWebpackPath(
flowResourcesFolder) + "');";
String assetsResourceFolder =
"const projectStaticAssetsOutputFolder = require('path').resolve(__dirname, '"
+ getEscapedRelativeWebpackPath(resourceFolder) + "');";

for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).startsWith(
"const fileNameOfTheFlowGeneratedMainEntryPoint")) {
lines.set(i, mainLine);
}
if (lines.get(i)
.startsWith("const mavenOutputFolderForFlowBundledFiles")) {
} else if (lines.get(i)
.startsWith("const mavenOutputFolderForFlowBundledFiles")) {
lines.set(i, outputLine);
}
if (lines.get(i).startsWith("const frontendFolder")) {
} else if (lines.get(i).startsWith("const frontendFolder")) {
lines.set(i, frontendLine);
}
if (lines.get(i).startsWith("const useClientSideIndexFileForBootstrapping")) {
} else if (lines.get(i)
.startsWith("const useClientSideIndexFileForBootstrapping")) {
lines.set(i, isClientSideBootstrapModeLine);
}
if (lines.get(i).startsWith("const clientSideIndexHTML")) {
} else if (lines.get(i).startsWith("const clientSideIndexHTML")) {
lines.set(i, getIndexHtmlPath());
}

if (lines.get(i).startsWith("const clientSideIndexEntryPoint")) {
} else if (lines.get(i)
.startsWith("const clientSideIndexEntryPoint")) {
lines.set(i, getClientEntryPoint());
}

if (lines.get(i).startsWith("const devmodeGizmoJS")) {
} else if (lines.get(i).startsWith("const devmodeGizmoJS")) {
lines.set(i, devModeGizmoJSLine);
} else if (lines.get(i).startsWith("const flowFrontendFolder")) {
lines.set(i, frontendFolder);
} else if (lines.get(i)
.startsWith("const projectStaticAssetsOutputFolder")) {
lines.set(i, assetsResourceFolder);
}
}
return lines;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ final class FrontendClassVisitor extends ClassVisitor {
private static final String VARIANT = "variant";
private static final String LAYOUT = "layout";
static final String VALUE = "value";
static final String THEME_CLASS = "themeClass";
static final String VERSION = "version";
static final String ID = "id";
static final String INCLUDE = "include";
Expand Down Expand Up @@ -160,8 +161,10 @@ public void visit(String name, Object value) {
@Override
pleku marked this conversation as resolved.
Show resolved Hide resolved
public void visit(String name, Object value) {
if (VALUE.equals(name)) {
endPoint.theme.name = ((Type) value).getClassName();
children.add(endPoint.theme.name);
endPoint.theme.themeName = (String)value;
} else if (THEME_CLASS.equals(name)) {
endPoint.theme.themeClass = ((Type) value).getClassName();
children.add(endPoint.theme.themeClass);
} else if (VARIANT.equals(name)) {
endPoint.theme.variant = value.toString();
}
Expand All @@ -171,7 +174,9 @@ public void visit(String name, Object value) {
themeLayoutVisitor = new RepeatedAnnotationVisitor() {
pleku marked this conversation as resolved.
Show resolved Hide resolved
@Override
public void visit(String name, Object value) {
if (VALUE.equals(name) && endPoint.theme.name == null) {
if (VALUE.equals(name)) {
themeRouteVisitor.visit(name, value);
} else if (THEME_CLASS.equals(name) && endPoint.theme.themeClass == null) {
themeRouteVisitor.visit(name, value);
} else if (VARIANT.equals(name)
&& endPoint.theme.variant.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,26 +267,28 @@ private void computeApplicationTheme() throws ClassNotFoundException,
visitClass(endPoint.getLayout(), endPoint, false);
}
if (endPoint.getTheme() != null) {
visitClass(endPoint.getTheme().getName(), endPoint, true);
visitClass(endPoint.getTheme().getThemeClass(), endPoint, true);
pleku marked this conversation as resolved.
Show resolved Hide resolved
}
}

Set<ThemeData> themes = endPoints.values().stream()
// consider only endPoints with theme information
.filter(data -> data.getTheme().getName() != null
.filter(data -> data.getTheme().getThemeClass() != null ||
(data.getTheme().getThemeName() != null && !data.getTheme().getThemeName().isEmpty())
|| data.getTheme().isNotheme())
.map(EndPointData::getTheme)
// Remove duplicates by returning a set
.collect(Collectors.toSet());

if (themes.size() > 1) {
String names = endPoints.values().stream()
.filter(data -> data.getTheme().getName() != null
.filter(data -> data.getTheme().getThemeClass() != null ||
data.getTheme().getThemeName() != null
pleku marked this conversation as resolved.
Show resolved Hide resolved
|| data.getTheme().isNotheme())
.map(data -> "found '"
+ (data.getTheme().isNotheme()
? NoTheme.class.getName()
: data.getTheme().getName())
: data.getTheme().getThemeName())
+ "' in '" + data.getName() + "'")
.collect(Collectors.joining("\n "));
throw new IllegalStateException(
Expand All @@ -296,21 +298,27 @@ private void computeApplicationTheme() throws ClassNotFoundException,

Class<? extends AbstractTheme> theme = null;
String variant = "";
String themeName = "";
if (themes.isEmpty()) {
theme = getDefaultTheme();
} else {
// we have a proper theme or no-theme for the app
ThemeData themeData = themes.iterator().next();
if (!themeData.isNotheme()) {
variant = themeData.getVariant();
theme = getFinder().loadClass(themeData.getName());
String themeClass = themeData.getThemeClass();
if (themeClass == null) {
themeClass = LUMO;
}
theme = getFinder().loadClass(themeClass);
themeName = themeData.getThemeName();
}

}

// theme could be null when lumo is not found or when a NoTheme found
if (theme != null) {
themeDefinition = new ThemeDefinition(theme, variant);
themeDefinition = new ThemeDefinition(theme, variant, themeName);
themeInstance = new ThemeWrapper(theme);
}
}
Expand Down
Loading