Skip to content

Commit 075686c

Browse files
feat: Introduce methods to get current assets structure
Introduce new methods in `projectDataService` to get the current assets strucuture. Use a predefined .json file in the resources to get information about image sizes. For iOS read the Contents.json file in specific iOS Resource directories. For Android - enumerate the files and construct required objects. Use the new method in the assetsGenerationService. Generate icons and splashes based on the argument. Require projectDir instead of resourcesDir for assets generation in order to get all CLI's specific files info (nsconfig, package.json, project location, etc.). Fix comands for generating assets to have mandatory command parameter - previously it had a non-mandatory param. Add newly exposed methods to the tests.
1 parent 25f63c3 commit 075686c

13 files changed

+695
-454
lines changed

lib/commands/generate-assets.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
1-
import { StringCommandParameter } from "../common/command-params";
2-
31
export abstract class GenerateCommandBase implements ICommand {
4-
public allowedParameters: ICommandParameter[] = [new StringCommandParameter(this.$injector)];
2+
public allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("You have to provide path to image to generate other images based on it.")];
53

64
constructor(protected $options: IOptions,
75
protected $injector: IInjector,
86
protected $projectData: IProjectData,
7+
protected $stringParameterBuilder: IStringParameterBuilder,
98
protected $assetsGenerationService: IAssetsGenerationService) {
109
this.$projectData.initializeProjectData();
1110
}
1211

1312
public async execute(args: string[]): Promise<void> {
1413
const [ imagePath ] = args;
15-
const resourcesPath = this.$projectData.getAppResourcesDirectoryPath();
16-
await this.generate(imagePath, resourcesPath, this.$options.background);
14+
await this.generate(imagePath, this.$options.background);
1715
}
1816

19-
protected abstract generate(imagePath: string, resourcesPath: string, background?: string): Promise<void>;
17+
protected abstract generate(imagePath: string, background?: string): Promise<void>;
2018
}
2119

2220
export class GenerateIconsCommand extends GenerateCommandBase implements ICommand {
2321
constructor(protected $options: IOptions,
2422
$injector: IInjector,
25-
$projectData: IProjectData,
23+
protected $projectData: IProjectData,
24+
protected $stringParameterBuilder: IStringParameterBuilder,
2625
$assetsGenerationService: IAssetsGenerationService) {
27-
super($options, $injector, $projectData, $assetsGenerationService);
26+
super($options, $injector, $projectData, $stringParameterBuilder, $assetsGenerationService);
2827
}
2928

30-
protected async generate(imagePath: string, resourcesPath: string, background?: string): Promise<void> {
31-
await this.$assetsGenerationService.generateIcons({ imagePath, resourcesPath });
29+
protected async generate(imagePath: string, background?: string): Promise<void> {
30+
await this.$assetsGenerationService.generateIcons({ imagePath, projectDir: this.$projectData.projectDir });
3231
}
3332
}
3433

@@ -37,13 +36,14 @@ $injector.registerCommand("resources|generate|icons", GenerateIconsCommand);
3736
export class GenerateSplashScreensCommand extends GenerateCommandBase implements ICommand {
3837
constructor(protected $options: IOptions,
3938
$injector: IInjector,
40-
$projectData: IProjectData,
39+
protected $projectData: IProjectData,
40+
protected $stringParameterBuilder: IStringParameterBuilder,
4141
$assetsGenerationService: IAssetsGenerationService) {
42-
super($options, $injector, $projectData, $assetsGenerationService);
42+
super($options, $injector, $projectData, $stringParameterBuilder, $assetsGenerationService);
4343
}
4444

4545
protected async generate(imagePath: string, resourcesPath: string, background?: string): Promise<void> {
46-
await this.$assetsGenerationService.generateSplashScreens({ imagePath, resourcesPath, background });
46+
await this.$assetsGenerationService.generateSplashScreens({ imagePath, background, projectDir: this.$projectData.projectDir });
4747
}
4848
}
4949

lib/constants.ts

+15
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,18 @@ export const NATIVESCRIPT_CLOUD_EXTENSION_NAME = "nativescript-cloud";
148148
* Used in ProjectDataService to concatenate the names of the properties inside nativescript key of package.json.
149149
*/
150150
export const NATIVESCRIPT_PROPS_INTERNAL_DELIMITER = "**|__**";
151+
export const CLI_RESOURCES_DIR_NAME = "resources";
152+
153+
export class AssetConstants {
154+
public static iOSResourcesFileName = "Contents.json";
155+
public static iOSAssetsDirName = "Assets.xcassets";
156+
public static iOSIconsDirName = "AppIcon.appiconset";
157+
public static iOSSplashBackgroundsDirName = "LaunchScreen.AspectFill.imageset";
158+
public static iOSSplashCenterImagesDirName = "LaunchScreen.Center.imageset";
159+
public static iOSSplashImagesDirName = "LaunchImage.launchimage";
160+
161+
public static imageDefinitionsFileName = "image-definitions.json";
162+
public static assets = "assets";
163+
164+
public static sizeDelimiter = "x";
165+
}

lib/declarations.d.ts

+6-10
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ interface IAndroidToolsInfo {
571571
*/
572572
validateAndroidHomeEnvVariable(options?: IAndroidToolsInfoOptions): boolean;
573573

574-
/**
574+
/**
575575
* Validates target sdk
576576
* @param {IAndroidToolsInfoOptions} options @optional Defines if the warning messages should treated as error.
577577
* @returns {boolean} True if there are detected issues, false otherwise
@@ -791,25 +791,22 @@ interface IBundleValidatorHelper {
791791
interface INativescriptCloudExtensionService {
792792
/**
793793
* Installs nativescript-cloud extension
794-
* @return {Promise<IExtensionData>} returns the extension data
794+
* @return {Promise<IExtensionData>} returns the extension data
795795
*/
796796
install(): Promise<IExtensionData>;
797797
}
798798

799799
/**
800800
* Describes the basic data needed for resource generation
801801
*/
802-
interface IResourceGenerationData {
802+
interface IResourceGenerationData extends IProjectDir {
803803
/**
804804
* @param {string} imagePath Path to the image that will be used for generation
805805
*/
806-
imagePath: string,
807-
/**
808-
* @param {string} resourcesPath Path to the app resources
809-
*/
810-
resourcesPath: string,
806+
imagePath: string,
807+
811808
/**
812-
* @param {string} platform Specify for which platform to generate assets. If not defined will generate for all platforms
809+
* @param {string} platform Specify for which platform to generate assets. If not defined will generate for all platforms
813810
*/
814811
platform?: string
815812
}
@@ -824,7 +821,6 @@ interface ISplashesGenerationData extends IResourceGenerationData {
824821
background?: string
825822
}
826823

827-
828824
/**
829825
* Describes service used for assets generation
830826
*/

lib/definitions/project.d.ts

+38-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ interface IProjectService {
5555

5656
interface INsConfig {
5757
appPath?: string;
58-
appResourcesPath?:string;
58+
appResourcesPath?: string;
5959
}
6060

6161
interface IProjectData extends IProjectDir {
@@ -123,6 +123,43 @@ interface IProjectDataService {
123123
removeDependency(projectDir: string, dependencyName: string): void;
124124

125125
getProjectData(projectDir?: string): IProjectData;
126+
127+
getAssetsStructure(opts: IProjectDir): Promise<IAssetsStructure>;
128+
129+
getIOSAssetsStructure(opts: IProjectDir): Promise<IAssetGroup>;
130+
131+
getAndroidAssetsStructure(opts: IProjectDir): Promise<IAssetGroup>;
132+
}
133+
134+
interface IAssetItem {
135+
path: string;
136+
size: string;
137+
width: number;
138+
height: number;
139+
filename: string;
140+
directory: string;
141+
scale: number;
142+
idiom: string;
143+
resizeOperation?: string;
144+
}
145+
146+
interface IAssetSubGroup {
147+
images: IAssetItem[];
148+
info?: { version: string, author: string };
149+
}
150+
151+
interface IAssetGroup {
152+
icons: IAssetSubGroup;
153+
splashBackgrounds: IAssetSubGroup;
154+
splashCenterImages: IAssetSubGroup;
155+
splashImages?: IAssetSubGroup;
156+
[imageType: string]: IAssetSubGroup;
157+
}
158+
159+
interface IAssetsStructure {
160+
ios: IAssetGroup;
161+
android: IAssetGroup;
162+
[platform: string]: IAssetGroup;
126163
}
127164

128165
/**

lib/services/assets-generation/assets-generation-service.ts

+71-38
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,102 @@
1-
import * as path from "path";
21
import * as Jimp from "jimp";
32
import * as Color from "color";
4-
import { Icons, SplashScreens, Operations } from "./image-definitions";
53
import { exported } from "../../common/decorators";
64

5+
export const enum Operations {
6+
OverlayWith = "overlayWith",
7+
Blank = "blank",
8+
Resize = "resize"
9+
}
10+
11+
interface IGenerateImagesData extends ISplashesGenerationData {
12+
propertiesToEnumerate: string[];
13+
}
14+
715
export class AssetsGenerationService implements IAssetsGenerationService {
16+
private get propertiesToEnumerate(): any {
17+
return {
18+
icon: ["icons"],
19+
splash: ["splashBackgrounds", "splashCenterImages", "splashImages"]
20+
};
21+
}
22+
823
constructor(private $logger: ILogger,
9-
private $androidResourcesMigrationService: IAndroidResourcesMigrationService) {
24+
private $projectDataService: IProjectDataService) {
1025
}
1126

1227
@exported("assetsGenerationService")
1328
public async generateIcons(resourceGenerationData: IResourceGenerationData): Promise<void> {
1429
this.$logger.info("Generating icons ...");
15-
await this.generateImagesForDefinitions(resourceGenerationData.imagePath, resourceGenerationData.resourcesPath, Icons, resourceGenerationData.platform);
30+
const generationData = (<IGenerateImagesData>resourceGenerationData);
31+
generationData.propertiesToEnumerate = this.propertiesToEnumerate.icon;
32+
await this.generateImagesForDefinitions(generationData);
1633
this.$logger.info("Icons generation completed.");
1734
}
1835

1936
@exported("assetsGenerationService")
2037
public async generateSplashScreens(splashesGenerationData: ISplashesGenerationData): Promise<void> {
2138
this.$logger.info("Generating splash screens ...");
22-
await this.generateImagesForDefinitions(splashesGenerationData.imagePath, splashesGenerationData.resourcesPath, SplashScreens, splashesGenerationData.platform, splashesGenerationData.background);
39+
const generationData = (<IGenerateImagesData>splashesGenerationData);
40+
generationData.propertiesToEnumerate = this.propertiesToEnumerate.splash;
41+
await this.generateImagesForDefinitions(generationData);
2342
this.$logger.info("Splash screens generation completed.");
2443
}
2544

26-
private async generateImagesForDefinitions(imagePath: string, resourcesPath: string, definitions: any[], platform?: string, background: string = "white") : Promise<void> {
27-
const hasMigrated = this.$androidResourcesMigrationService.hasMigrated(resourcesPath);
28-
29-
const filteredDefinitions = platform ? _.filter(definitions, definition => _.toLower(definition.platform) === _.toLower(platform)) : definitions;
30-
31-
for (const definition of filteredDefinitions) {
32-
const operation = definition.operation || Operations.Resize;
33-
const scale = definition.scale || 0.8;
34-
const path = hasMigrated ? definition.path : (definition.pathBeforeMigration || definition.path);
35-
const outputPath = this.convertToAbsolutePath(resourcesPath, path);
36-
37-
switch (operation) {
38-
case Operations.OverlayWith:
39-
const imageResize = Math.round(Math.min(definition.width, definition.height) * scale);
40-
const image = await this.resize(imagePath, imageResize, imageResize);
41-
await this.generateImage(background, definition.width, definition.height, outputPath, image);
42-
break;
43-
case Operations.Blank:
44-
await this.generateImage(background, definition.width, definition.height, outputPath);
45-
break;
46-
case Operations.Resize:
47-
const resizedImage = await this.resize(imagePath, definition.width, definition.height);
48-
resizedImage.write(outputPath);
49-
break;
50-
default:
51-
throw new Error(`Invalid image generation operation: ${operation}`);
45+
private async generateImagesForDefinitions(data: IGenerateImagesData): Promise<void> {
46+
data.background = data.background || "white";
47+
const assetsStructure = await this.$projectDataService.getAssetsStructure({ projectDir: data.projectDir });
48+
49+
for (const platform in assetsStructure) {
50+
if (data.platform && platform.toLowerCase() !== data.platform.toLowerCase()) {
51+
continue;
52+
}
53+
54+
const platformAssetsStructure = assetsStructure[platform];
55+
56+
for (const imageTypeKey in platformAssetsStructure) {
57+
if (data.propertiesToEnumerate.indexOf(imageTypeKey) === -1 || !platformAssetsStructure[imageTypeKey]) {
58+
continue;
59+
}
60+
61+
const imageType = platformAssetsStructure[imageTypeKey];
62+
63+
for (const assetItem of imageType.images) {
64+
if (!assetItem.filename) {
65+
continue;
66+
}
67+
68+
const operation = assetItem.resizeOperation || Operations.Resize;
69+
const scale = assetItem.scale || 0.8;
70+
const outputPath = assetItem.path;
71+
72+
switch (operation) {
73+
case Operations.OverlayWith:
74+
const imageResize = Math.round(Math.min(assetItem.width, assetItem.height) * scale);
75+
const image = await this.resize(data.imagePath, imageResize, imageResize);
76+
await this.generateImage(data.background, assetItem.width, assetItem.height, outputPath, image);
77+
break;
78+
case Operations.Blank:
79+
await this.generateImage(data.background, assetItem.width, assetItem.height, outputPath);
80+
break;
81+
case Operations.Resize:
82+
const resizedImage = await this.resize(data.imagePath, assetItem.width, assetItem.height);
83+
resizedImage.write(outputPath);
84+
break;
85+
default:
86+
throw new Error(`Invalid image generation operation: ${operation}`);
87+
}
88+
}
5289
}
5390
}
5491
}
5592

56-
private async resize(imagePath: string, width: number, height: number) : Promise<Jimp> {
93+
private async resize(imagePath: string, width: number, height: number): Promise<Jimp> {
5794
const image = await Jimp.read(imagePath);
5895
return image.scaleToFit(width, height);
5996
}
6097

6198
private generateImage(background: string, width: number, height: number, outputPath: string, overlayImage?: Jimp): void {
62-
//Typescript declarations for Jimp are not updated to define the constructor with backgroundColor so we workaround it by casting it to <any> for this case only.
99+
// Typescript declarations for Jimp are not updated to define the constructor with backgroundColor so we workaround it by casting it to <any> for this case only.
63100
const J = <any>Jimp;
64101
const backgroundColor = this.getRgbaNumber(background);
65102
let image = new J(width, height, backgroundColor);
@@ -73,11 +110,7 @@ export class AssetsGenerationService implements IAssetsGenerationService {
73110
image.write(outputPath);
74111
}
75112

76-
private convertToAbsolutePath(resourcesPath: string, resourcePath: string) {
77-
return path.isAbsolute(resourcePath) ? resourcePath : path.join(resourcesPath, resourcePath);
78-
}
79-
80-
private getRgbaNumber(colorString: string) : number {
113+
private getRgbaNumber(colorString: string): number {
81114
const color = new Color(colorString);
82115
const colorRgb = color.rgb();
83116
const alpha = Math.round(colorRgb.alpha() * 255);

0 commit comments

Comments
 (0)