-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat!: refactor api * chore: remove log from generate-assets * chore: include original image name in the instruction * chore: cleanup generate-assets * chore: include pwa manifest icons + move some tests * chore: split instructor resolver * chore: rename icons resolver module * chore: add manifest CLI option
- Loading branch information
Showing
21 changed files
with
1,217 additions
and
543 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
import type { PngOptions, ResizeOptions } from 'sharp' | ||
import type { AppleDeviceSize, AppleSplashScreens, ResolvedAppleSplashScreens } from '../types.ts' | ||
import { defaultSplashScreenName } from '../splash.ts' | ||
import { createPngCompressionOptions, createResizeOptions } from './defaults.ts' | ||
import type { ImageAssets, ImageAssetsInstructions } from './types.ts' | ||
import { generateMaskableAsset } from './maskable.ts' | ||
import { createAppleSplashScreenHtmlLink } from './html.ts' | ||
|
||
export function resolveAppleSplashScreensInstructions( | ||
imageAssets: ImageAssets, | ||
instructions: ImageAssetsInstructions, | ||
useAppleSplashScreens?: AppleSplashScreens, | ||
) { | ||
const appleSplashScreens = resolveAppleSplashScreens(useAppleSplashScreens) | ||
if (!appleSplashScreens || !appleSplashScreens.sizes.length) | ||
return | ||
|
||
const { linkMediaOptions, name: resolveName, sizes, png } = appleSplashScreens | ||
|
||
const sizesMap = new Map<number, number>() | ||
const splashScreens: SplashScreenData[] = sizes.reduce((acc, size) => { | ||
// cleanup duplicates screen dimensions: | ||
// should we add the links (maybe scaleFactor is different)? | ||
if (sizesMap.get(size.width) === size.height) | ||
return acc | ||
|
||
sizesMap.set(size.width, size.height) | ||
const { width: height, height: width, ...restSize } = size | ||
const { | ||
width: lheight, | ||
height: lwidth, | ||
...restResizeOptions | ||
} = size.resizeOptions || {} | ||
const landscapeSize: AppleDeviceSize = { | ||
...restSize, | ||
width, | ||
height, | ||
resizeOptions: { | ||
...restResizeOptions, | ||
width: lwidth, | ||
height: lheight, | ||
}, | ||
} | ||
acc.push({ | ||
size, | ||
landscape: false, | ||
dark: size.darkResizeOptions ? false : undefined, | ||
resizeOptions: size.resizeOptions, | ||
padding: size.padding ?? 0.3, | ||
png: size.png ?? png, | ||
}) | ||
acc.push({ | ||
size: landscapeSize, | ||
landscape: true, | ||
dark: size.darkResizeOptions ? false : undefined, | ||
resizeOptions: landscapeSize.resizeOptions, | ||
padding: size.padding ?? 0.3, | ||
png: size.png ?? png, | ||
}) | ||
if (size.darkResizeOptions) { | ||
const { | ||
width: dlheight, | ||
height: dlwidth, | ||
...restDarkResizeOptions | ||
} = size.darkResizeOptions | ||
const landscapeDarkResizeOptions: ResizeOptions = { ...restDarkResizeOptions, width: dlwidth, height: dlheight } | ||
const landscapeDarkSize: AppleDeviceSize = { | ||
...restSize, | ||
width, | ||
height, | ||
resizeOptions: landscapeDarkResizeOptions, | ||
darkResizeOptions: undefined, | ||
} | ||
acc.push({ | ||
size, | ||
landscape: false, | ||
dark: true, | ||
resizeOptions: size.darkResizeOptions, | ||
padding: size.padding ?? 0.3, | ||
png: size.png ?? png, | ||
}) | ||
acc.push({ | ||
size: landscapeDarkSize, | ||
landscape: true, | ||
dark: true, | ||
resizeOptions: landscapeDarkResizeOptions, | ||
padding: size.padding ?? 0.3, | ||
png: size.png ?? png, | ||
}) | ||
} | ||
return acc | ||
}, [] as SplashScreenData[]) | ||
|
||
sizesMap.clear() | ||
|
||
for (const size of splashScreens) { | ||
const name = resolveName(size.landscape, size.size, size.dark) | ||
const url = `${imageAssets.basePath}${name}` | ||
const promise = () => generateMaskableAsset('png', imageAssets.imageName, size.size, { | ||
padding: size.padding, | ||
resizeOptions: { | ||
...size.resizeOptions, | ||
background: size.resizeOptions?.background ?? (size.dark ? 'black' : 'white'), | ||
}, | ||
outputOptions: size.png, | ||
}) | ||
instructions.appleSplashScreen[url] = { | ||
name, | ||
url, | ||
width: size.size.width, | ||
height: size.size.height, | ||
mimeType: 'image/png', | ||
link: createAppleSplashScreenHtmlLink('string', { | ||
size: size.size, | ||
landscape: size.landscape, | ||
addMediaScreen: linkMediaOptions.addMediaScreen, | ||
xhtml: linkMediaOptions.xhtml, | ||
name: resolveName, | ||
basePath: linkMediaOptions.basePath, | ||
dark: size.dark, | ||
includeId: imageAssets.htmlLinks.includeId, | ||
}), | ||
linkObject: createAppleSplashScreenHtmlLink('link', { | ||
size: size.size, | ||
landscape: size.landscape, | ||
addMediaScreen: linkMediaOptions.addMediaScreen, | ||
xhtml: linkMediaOptions.xhtml, | ||
name: resolveName, | ||
basePath: linkMediaOptions.basePath, | ||
dark: size.dark, | ||
includeId: imageAssets.htmlLinks.includeId, | ||
}), | ||
buffer: () => promise().then(m => m.toBuffer()), | ||
} | ||
} | ||
} | ||
|
||
interface SplashScreenData { | ||
size: AppleDeviceSize | ||
landscape: boolean | ||
dark?: boolean | ||
resizeOptions?: ResizeOptions | ||
padding: number | ||
png: PngOptions | ||
} | ||
|
||
function resolveAppleSplashScreens( | ||
useAppleSplashScreens?: AppleSplashScreens, | ||
) { | ||
let appleSplashScreens: ResolvedAppleSplashScreens | undefined | ||
if (useAppleSplashScreens) { | ||
const { | ||
padding = 0.3, | ||
resizeOptions: useResizeOptions = {}, | ||
darkResizeOptions: useDarkResizeOptions = {}, | ||
linkMediaOptions: useLinkMediaOptions = {}, | ||
sizes, | ||
name = defaultSplashScreenName, | ||
png: usePng = {}, | ||
} = useAppleSplashScreens | ||
|
||
// Initialize defaults | ||
const resizeOptions = createResizeOptions(false, useResizeOptions) | ||
const darkResizeOptions = createResizeOptions(true, useDarkResizeOptions) | ||
const png: PngOptions = createPngCompressionOptions(usePng) | ||
|
||
for (const size of sizes) { | ||
if (typeof size.padding === 'undefined') | ||
size.padding = padding | ||
|
||
if (typeof size.png === 'undefined') | ||
size.png = png | ||
|
||
if (typeof size.resizeOptions === 'undefined') | ||
size.resizeOptions = resizeOptions | ||
|
||
if (typeof size.darkResizeOptions === 'undefined') | ||
size.darkResizeOptions = darkResizeOptions | ||
} | ||
const { | ||
log = true, | ||
addMediaScreen = true, | ||
basePath = '/', | ||
xhtml = false, | ||
} = useLinkMediaOptions | ||
appleSplashScreens = { | ||
padding, | ||
sizes, | ||
linkMediaOptions: { | ||
log, | ||
addMediaScreen, | ||
basePath, | ||
xhtml, | ||
}, | ||
name, | ||
png, | ||
} | ||
} | ||
|
||
return appleSplashScreens | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { resolve } from 'node:path' | ||
import { existsSync } from 'node:fs' | ||
import { writeFile } from 'node:fs/promises' | ||
import type { IconAsset, ImageAssetsInstructions } from './types.ts' | ||
|
||
export async function generateAssets( | ||
instruction: ImageAssetsInstructions, | ||
overrideAssets: boolean, | ||
folder: string, | ||
log?: (message: string, ignored: boolean) => void, | ||
) { | ||
const transparent = Array.from(Object.values(instruction.transparent)) | ||
await Promise.all(transparent.map(icon => generateAsset('PNG', icon, folder, overrideAssets, log))) | ||
const maskable = Array.from(Object.values(instruction.maskable)) | ||
await Promise.all(maskable.map(icon => generateAsset('PNG', icon, folder, overrideAssets, log))) | ||
const apple = Array.from(Object.values(instruction.apple)) | ||
await Promise.all(apple.map(icon => generateAsset('PNG', icon, folder, overrideAssets, log))) | ||
const favicon = Array.from(Object.values(instruction.favicon)) | ||
await Promise.all(favicon.filter(icon => !icon.name.endsWith('.svg')).map(icon => generateAsset('ICO', icon, folder, overrideAssets, log))) | ||
const appleSplashScreen = Array.from(Object.values(instruction.appleSplashScreen)) | ||
await Promise.all(appleSplashScreen.map(icon => generateAsset('PNG', icon, folder, overrideAssets, log))) | ||
} | ||
|
||
async function generateAsset( | ||
type: 'ICO' | 'PNG', | ||
icon: IconAsset<any>, | ||
folder: string, | ||
overrideAssets: boolean, | ||
log?: (message: string, ignored: boolean) => void, | ||
) { | ||
const filePath = resolve(folder, icon.name) | ||
if (!overrideAssets && existsSync(filePath)) { | ||
log?.(`Skipping, ${type} file already exists: ${filePath}`, true) | ||
return | ||
} | ||
|
||
await icon | ||
.buffer() | ||
.then(b => writeFile(resolve(folder, icon.name), b)) | ||
.then(() => {}) | ||
|
||
log?.(`Generated ${type} file: ${filePath}`, false) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import type { ImageAssetsInstructions } from './types.ts' | ||
|
||
export function generateHtmlMarkup(instruction: ImageAssetsInstructions) { | ||
const apple = Array.from(Object.values(instruction.apple)) | ||
const favicon = Array.from(Object.values(instruction.favicon)) | ||
const appleSplashScreen = Array.from(Object.values(instruction.appleSplashScreen)) | ||
const links: string[] = [] | ||
favicon.forEach(icon => icon.link?.length && links.push(icon.link)) | ||
apple.forEach(icon => icon.link?.length && links.push(icon.link)) | ||
appleSplashScreen.forEach(icon => icon.link?.length && links.push(icon.link)) | ||
|
||
return links | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import type { ImageAssetsInstructions, ManifestIcons, ManifestIconsOptionsType, ManifestIconsType } from './types.ts' | ||
|
||
export function generateManifestIconsEntry<Format extends ManifestIconsType>( | ||
format: Format, | ||
instruction: ImageAssetsInstructions, | ||
): ManifestIconsOptionsType<Format> { | ||
const icons: ManifestIcons = { icons: [] } | ||
|
||
for (const icon of Object.values(instruction.transparent)) { | ||
icons.icons.push({ | ||
src: icon.name, | ||
sizes: `${icon.width}x${icon.height}`, | ||
type: icon.mimeType, | ||
}) | ||
} | ||
for (const icon of Object.values(instruction.maskable)) { | ||
icons.icons.push({ | ||
src: icon.name, | ||
sizes: `${icon.width}x${icon.height}`, | ||
type: icon.mimeType, | ||
purpose: 'maskable', | ||
}) | ||
} | ||
|
||
return ( | ||
format === 'string' | ||
? JSON.stringify(icons, null, 2) | ||
: icons | ||
) as ManifestIconsOptionsType<Format> | ||
} |
Oops, something went wrong.