react-native-bootsplash 5.0.0-beta.4 → 5.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +8 -6
  2. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashDialog.java +8 -4
  3. package/android/src/main/java/com/zoontek/rnbootsplash/RNBootSplashModuleImpl.java +1 -0
  4. package/android/src/main/res/drawable/transparent_pixel.png +0 -0
  5. package/android/src/main/res/values/public.xml +2 -0
  6. package/android/src/main/res/values/styles.xml +11 -4
  7. package/android/src/main/res/values-v23/styles.xml +11 -0
  8. package/android/src/main/res/values-v27/styles.xml +8 -0
  9. package/android/src/main/res/values-v31/styles.xml +3 -1
  10. package/dist/commonjs/NativeRNBootSplash.web.js +43 -0
  11. package/dist/commonjs/NativeRNBootSplash.web.js.map +1 -0
  12. package/dist/commonjs/addon/index.js +82 -1
  13. package/dist/commonjs/addon/index.js.map +1 -1
  14. package/dist/commonjs/generate.js +139 -28
  15. package/dist/commonjs/generate.js.map +1 -1
  16. package/dist/module/NativeRNBootSplash.web.js +36 -0
  17. package/dist/module/NativeRNBootSplash.web.js.map +1 -0
  18. package/dist/module/addon/index.js +82 -1
  19. package/dist/module/addon/index.js.map +1 -1
  20. package/dist/module/generate.js +138 -28
  21. package/dist/module/generate.js.map +1 -1
  22. package/dist/typescript/NativeRNBootSplash.web.d.ts +10 -0
  23. package/dist/typescript/NativeRNBootSplash.web.d.ts.map +1 -0
  24. package/dist/typescript/addon/index.d.ts +1 -1
  25. package/dist/typescript/addon/index.d.ts.map +1 -1
  26. package/dist/typescript/generate.d.ts +18 -6
  27. package/dist/typescript/generate.d.ts.map +1 -1
  28. package/package.json +14 -11
  29. package/react-native.config.js +15 -10
  30. package/src/NativeRNBootSplash.web.ts +42 -0
  31. package/src/generate.ts +182 -48
package/src/generate.ts CHANGED
@@ -5,18 +5,20 @@ import {
5
5
  } from "@react-native-community/cli-types";
6
6
  import detectIndent from "detect-indent";
7
7
  import fs from "fs-extra";
8
+ import { parse as parseHtml } from "node-html-parser";
8
9
  import path from "path";
9
10
  import pc from "picocolors";
10
- import type { Sharp } from "sharp";
11
- import sharp from "sharp";
12
- import xmlFormat, { XMLFormatterOptions } from "xml-formatter";
13
- import type { Manifest } from ".";
11
+ import { Options as PrettierOptions } from "prettier";
12
+ import htmlPlugin from "prettier/parser-html";
13
+ import cssPlugin from "prettier/parser-postcss";
14
+ import prettier from "prettier/standalone";
15
+ import sharp, { Sharp } from "sharp";
16
+ import { dedent } from "ts-dedent";
17
+ import formatXml, { XMLFormatterOptions } from "xml-formatter";
18
+ import { Manifest } from ".";
14
19
 
15
20
  const workingPath = process.env.INIT_CWD ?? process.env.PWD ?? process.cwd();
16
21
 
17
- export const androidColorRegex =
18
- /<color name="bootsplash_background">#\w+<\/color>/g;
19
-
20
22
  export type Color = {
21
23
  hex: string;
22
24
  rgb: { R: string; G: string; B: string };
@@ -54,7 +56,8 @@ const getStoryboard = ({
54
56
  const logoX = (frameWidth - logoWidth) / 2;
55
57
  const logoY = (frameHeight - logoHeight) / 2;
56
58
 
57
- return `<?xml version="1.0" encoding="UTF-8"?>
59
+ return dedent`
60
+ <?xml version="1.0" encoding="UTF-8"?>
58
61
  <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
59
62
  <device id="retina4_7" orientation="portrait" appearance="light"/>
60
63
  <dependencies>
@@ -123,17 +126,13 @@ export const writeJson = (file: string, json: object) => {
123
126
 
124
127
  export const readXml = (file: string) => {
125
128
  const xml = fs.readFileSync(file, "utf-8");
129
+ const { indent } = detectIndent(xml);
126
130
 
127
- const minified = xmlFormat(xml, {
128
- collapseContent: true,
129
- forceSelfClosingEmptyTag: true,
130
- indentation: "",
131
- lineSeparator: "",
132
- whiteSpaceAtEndOfSelfclosingTag: true,
133
- });
131
+ const formatOptions: XMLFormatterOptions = {
132
+ indentation: indent || " ",
133
+ };
134
134
 
135
- const indentation = detectIndent(xml).indent || " ";
136
- return { minified, indentation };
135
+ return { root: parseHtml(xml), formatOptions };
137
136
  };
138
137
 
139
138
  export const writeXml = (
@@ -141,7 +140,7 @@ export const writeXml = (
141
140
  xml: string,
142
141
  options?: XMLFormatterOptions,
143
142
  ) => {
144
- const formatted = xmlFormat(xml, {
143
+ const formatted = formatXml(xml, {
145
144
  collapseContent: true,
146
145
  forceSelfClosingEmptyTag: true,
147
146
  indentation: " ",
@@ -154,6 +153,35 @@ export const writeXml = (
154
153
  logWrite(file);
155
154
  };
156
155
 
156
+ export const readHtml = (file: string) => {
157
+ const html = fs.readFileSync(file, "utf-8");
158
+ const { type, amount } = detectIndent(html);
159
+
160
+ const formatOptions: PrettierOptions = {
161
+ useTabs: type === "tab",
162
+ tabWidth: amount || 2,
163
+ };
164
+
165
+ return { root: parseHtml(html), formatOptions };
166
+ };
167
+
168
+ export const writeHtml = (
169
+ file: string,
170
+ html: string,
171
+ options?: Omit<PrettierOptions, "parser" | "plugins">,
172
+ ) => {
173
+ const formatted = prettier.format(html, {
174
+ parser: "html",
175
+ plugins: [htmlPlugin, cssPlugin],
176
+ tabWidth: 2,
177
+ useTabs: false,
178
+ ...options,
179
+ });
180
+
181
+ fs.writeFileSync(file, formatted, "utf-8");
182
+ logWrite(file);
183
+ };
184
+
157
185
  const ensureSupportedFormat = async (
158
186
  name: string,
159
187
  image: Sharp | undefined,
@@ -240,19 +268,41 @@ const getIOSProjectPath = (ios: IOSProjectConfig): string | undefined => {
240
268
  }
241
269
  };
242
270
 
271
+ const getHtmlTemplatePath = (html: string): string | undefined => {
272
+ const htmlTemplatePath = path.resolve(workingPath, html);
273
+
274
+ if (!fs.existsSync(htmlTemplatePath)) {
275
+ log.warn(
276
+ `No ${path.relative(
277
+ workingPath,
278
+ htmlTemplatePath,
279
+ )} file found. Skipping HTML + CSS generation…`,
280
+ );
281
+ } else {
282
+ return htmlTemplatePath;
283
+ }
284
+ };
285
+
243
286
  export type AddonConfig = {
244
287
  licenseKey: string;
245
288
 
246
289
  androidResPath: string | undefined;
247
290
  iosProjectPath: string | undefined;
291
+ htmlTemplatePath: string | undefined;
248
292
  assetsOutputPath: string | undefined;
249
293
 
294
+ logoPath: string;
295
+ darkLogoPath: string | undefined;
296
+ brandPath: string | undefined;
297
+ darkBrandPath: string | undefined;
298
+
250
299
  logoHeight: number;
251
300
  logoWidth: number;
252
301
  brandHeight: number;
253
302
  brandWidth: number;
254
303
 
255
304
  background: Color;
305
+ logo: Sharp;
256
306
  brand: Sharp | undefined;
257
307
 
258
308
  darkBackground: Color | undefined;
@@ -272,11 +322,12 @@ const requireAddon = ():
272
322
  };
273
323
 
274
324
  export const generate: CommandFunction<{
275
- logoWidth: number;
325
+ platforms: string[];
276
326
  background: string;
327
+ logoWidth: number;
277
328
  assetsOutput?: string;
329
+ html: string;
278
330
  flavor: string;
279
- platforms: string[];
280
331
 
281
332
  licenseKey?: string;
282
333
  brand?: string;
@@ -287,7 +338,7 @@ export const generate: CommandFunction<{
287
338
  }> = async (
288
339
  [argsLogo],
289
340
  { project: { android, ios } },
290
- { flavor, platforms, licenseKey, ...args },
341
+ { platforms, html, flavor, licenseKey, ...args },
291
342
  ) => {
292
343
  const [nodeStringVersion = ""] = process.versions.node.split(".");
293
344
  const nodeVersion = parseInt(nodeStringVersion, 10);
@@ -302,34 +353,38 @@ export const generate: CommandFunction<{
302
353
  process.exit(1);
303
354
  }
304
355
 
356
+ const logoPath = path.resolve(workingPath, argsLogo);
357
+
358
+ const darkLogoPath =
359
+ args.darkLogo != null
360
+ ? path.resolve(workingPath, args.darkLogo)
361
+ : undefined;
362
+
363
+ const brandPath =
364
+ args.brand != null ? path.resolve(workingPath, args.brand) : undefined;
365
+
366
+ const darkBrandPath =
367
+ args.darkBrand != null
368
+ ? path.resolve(workingPath, args.darkBrand)
369
+ : undefined;
370
+
305
371
  const assetsOutputPath =
306
372
  args.assetsOutput != null
307
373
  ? path.resolve(workingPath, args.assetsOutput)
308
374
  : undefined;
309
375
 
376
+ const logo = sharp(logoPath);
377
+ const darkLogo = darkLogoPath != null ? sharp(darkLogoPath) : undefined;
378
+ const brand = brandPath != null ? sharp(brandPath) : undefined;
379
+ const darkBrand = darkBrandPath != null ? sharp(darkBrandPath) : undefined;
380
+
310
381
  const background = parseColor(args.background);
311
- const logo = sharp(path.resolve(workingPath, argsLogo));
312
382
  const logoWidth = args.logoWidth - (args.logoWidth % 2);
313
383
  const brandWidth = args.brandWidth - (args.brandWidth % 2);
314
384
 
315
- const brand =
316
- args.brand != null
317
- ? sharp(path.resolve(workingPath, args.brand))
318
- : undefined;
319
-
320
385
  const darkBackground =
321
386
  args.darkBackground != null ? parseColor(args.darkBackground) : undefined;
322
387
 
323
- const darkLogo =
324
- args.darkLogo != null
325
- ? sharp(path.resolve(workingPath, args.darkLogo))
326
- : undefined;
327
-
328
- const darkBrand =
329
- args.darkBrand != null
330
- ? sharp(path.resolve(workingPath, args.darkBrand))
331
- : undefined;
332
-
333
388
  const executeAddon =
334
389
  brand != null ||
335
390
  darkBackground != null ||
@@ -409,6 +464,10 @@ export const generate: CommandFunction<{
409
464
  ? getIOSProjectPath(ios)
410
465
  : undefined;
411
466
 
467
+ const htmlTemplatePath = platforms.includes("web")
468
+ ? getHtmlTemplatePath(html)
469
+ : undefined;
470
+
412
471
  if (androidResPath != null) {
413
472
  log.title("🤖", "Android");
414
473
 
@@ -419,13 +478,19 @@ export const generate: CommandFunction<{
419
478
  const colorsXmlEntry = `<color name="bootsplash_background">${background.hex}</color>`;
420
479
 
421
480
  if (fs.existsSync(colorsXmlPath)) {
422
- const { minified: colorsXml, indentation } = readXml(colorsXmlPath);
423
-
424
- const nextXml = colorsXml.match(androidColorRegex)
425
- ? colorsXml.replace(androidColorRegex, colorsXmlEntry)
426
- : colorsXml.replace(/<\/resources>/g, `${colorsXmlEntry}</resources>`);
427
-
428
- writeXml(colorsXmlPath, nextXml, { indentation });
481
+ const { root, formatOptions } = readXml(colorsXmlPath);
482
+ const nextColor = parseHtml(colorsXmlEntry);
483
+ const prevColor = root.querySelector(
484
+ 'color[name="bootsplash_background"]',
485
+ );
486
+
487
+ if (prevColor != null) {
488
+ prevColor.replaceWith(nextColor);
489
+ } else {
490
+ root.querySelector("resources")?.appendChild(nextColor);
491
+ }
492
+
493
+ writeXml(colorsXmlPath, root.toString(), formatOptions);
429
494
  } else {
430
495
  writeXml(colorsXmlPath, `<resources>${colorsXmlEntry}</resources>`);
431
496
  }
@@ -493,18 +558,16 @@ export const generate: CommandFunction<{
493
558
  "BootSplash.storyboard",
494
559
  );
495
560
 
496
- fs.writeFileSync(
561
+ writeXml(
497
562
  storyboardPath,
498
563
  getStoryboard({
499
564
  logoHeight,
500
565
  logoWidth,
501
566
  background: background.rgb,
502
567
  }),
503
- "utf-8",
568
+ { whiteSpaceAtEndOfSelfclosingTag: false },
504
569
  );
505
570
 
506
- logWrite(storyboardPath);
507
-
508
571
  const imageSetPath = path.resolve(
509
572
  iosProjectPath,
510
573
  "Images.xcassets",
@@ -560,6 +623,70 @@ export const generate: CommandFunction<{
560
623
  );
561
624
  }
562
625
 
626
+ if (htmlTemplatePath != null) {
627
+ log.title("🌐", "Web");
628
+
629
+ const { root, formatOptions } = readHtml(htmlTemplatePath);
630
+ const { format } = await logo.metadata();
631
+ const prevStyle = root.querySelector("#bootsplash-style");
632
+
633
+ const base64 = (
634
+ format === "svg"
635
+ ? fs.readFileSync(logoPath)
636
+ : await logo
637
+ .clone()
638
+ .resize(Math.round(logoWidth * 2))
639
+ .png({ quality: 100 })
640
+ .toBuffer()
641
+ ).toString("base64");
642
+
643
+ const dataURI = `data:image/${format ? "svg+xml" : "png"};base64,${base64}`;
644
+
645
+ const nextStyle = parseHtml(dedent`
646
+ <style id="bootsplash-style">
647
+ #bootsplash {
648
+ position: absolute;
649
+ top: 0;
650
+ bottom: 0;
651
+ left: 0;
652
+ right: 0;
653
+ overflow: hidden;
654
+ display: flex;
655
+ justify-content: center;
656
+ align-items: center;
657
+ background-color: ${background.hex};
658
+ }
659
+ #bootsplash-logo {
660
+ content: url("${dataURI}");
661
+ width: ${logoWidth}px;
662
+ height: ${logoHeight}px;
663
+ }
664
+ </style>
665
+ `);
666
+
667
+ if (prevStyle != null) {
668
+ prevStyle.replaceWith(nextStyle);
669
+ } else {
670
+ root.querySelector("head")?.appendChild(nextStyle);
671
+ }
672
+
673
+ const prevDiv = root.querySelector("#bootsplash");
674
+
675
+ const nextDiv = parseHtml(dedent`
676
+ <div id="bootsplash">
677
+ <div id="bootsplash-logo"></div>
678
+ </div>
679
+ `);
680
+
681
+ if (prevDiv != null) {
682
+ prevDiv.replaceWith(nextDiv);
683
+ } else {
684
+ root.querySelector("body")?.appendChild(nextDiv);
685
+ }
686
+
687
+ writeHtml(htmlTemplatePath, root.toString(), formatOptions);
688
+ }
689
+
563
690
  if (assetsOutputPath != null) {
564
691
  log.title("📄", "Assets");
565
692
 
@@ -606,6 +733,7 @@ export const generate: CommandFunction<{
606
733
 
607
734
  androidResPath,
608
735
  iosProjectPath,
736
+ htmlTemplatePath,
609
737
  assetsOutputPath,
610
738
 
611
739
  logoHeight,
@@ -613,7 +741,13 @@ export const generate: CommandFunction<{
613
741
  brandHeight,
614
742
  brandWidth,
615
743
 
744
+ logoPath,
745
+ darkLogoPath,
746
+ brandPath,
747
+ darkBrandPath,
748
+
616
749
  background,
750
+ logo,
617
751
  brand,
618
752
 
619
753
  darkBackground,