fluent-emoji/index.ts

150 lines
No EOL
7.5 KiB
TypeScript

import { mkdir, exists, readdir } from "node:fs/promises"
import * as path from 'path'
// setup output dirs
const distDir = process.env.DIST_DIR || './dist'
if(!await exists(distDir)) await mkdir(distDir)
const emojiDir = distDir + '/emoji'
if(!await exists(emojiDir)) await mkdir(emojiDir)
const cssDir = distDir + '/css'
if(!await exists(cssDir)) await mkdir(cssDir)
// setup input dirs
const sourcesDir = (process.env.SOURCES_DIR || './sources')
const fluentDir = (process.env.FLUENTUI_EMOJI_DIR || sourcesDir + '/fluentui-emoji') + '/assets'
const fluentList = await readdir(fluentDir)
const animatedDir = (process.env.ANIMATED_FLUENT_EMOJIS_DIR || sourcesDir + '/Animated-Fluent-Emojis') + '/Emojis'
const animatedCatList = await readdir(animatedDir)
// generate a list of all the animated emojis,
// so we don't have to search through the folder later
const animatedList:Map<string, string> = new Map<string, string>()
for(let c in animatedCatList) {
const category = animatedCatList[c]
const catDir = `${animatedDir}/${category}`
const catFiles = await readdir(catDir)
catFiles.forEach((filename) => {
let key = path.parse(filename).name.toLowerCase()//.replace(/[\(\)'’“”]/g, '')
animatedList.set(key, `${catDir}/${filename}`)
})
}
// set up the root url for the css rules
const rootURL = process.env.ROOT_URL || "https://flumoji.pages.dev"
const cssRules = {
animated: [] as string[],
threeD: [] as string[],
color: [] as string[],
flat: [] as string[],
highContrast: [] as string[],
highContrastMask: [] as string[]
}
const htmlTags = [] as string[]
// now, we're going to loop through all the emoji from the official repo
await Promise.all(fluentList.map(async (folder) => {
// gather the necessary information
let thisEmojiIn = `${fluentDir}/${folder}`
const metadataFile = Bun.file(`${thisEmojiIn}/metadata.json`)
const metadata = await metadataFile.json()
const thisEmojiDir = metadata.unicode.replaceAll(' ', '-')
const thisEmojiOut = `${emojiDir}/${thisEmojiDir}`
// create the output directory
if(!await exists(thisEmojiOut)) await mkdir(thisEmojiOut)
// copy the metadata across (not necessary?)
if(!await exists(`${thisEmojiOut}/metadata.json`)) await Bun.write(`${thisEmojiOut}/metadata.json`, metadataFile)
// for the purposes of this excersise, I'm only worried about default skin-tone
if(metadata.unicodeSkintones) thisEmojiIn = `${thisEmojiIn}/Default`
const in3d = `${thisEmojiIn}/3D`
const inColor = `${thisEmojiIn}/Color`
const inFlat = `${thisEmojiIn}/Flat`
const inHighContrast = `${thisEmojiIn}/High Contrast`
// we have to search for the appropriate animated folder
const inAnim = metadata.tts.toLowerCase().replace(/[:]/g, '')
// get the correct image file
let imgAnimPath = animatedList.get(inAnim)
const img3DPath = `${in3d}/${(await readdir(in3d))[0]}`
const imgColorPath = `${inColor}/${(await readdir(inColor))[0]}`
const imgFlatPath = `${inFlat}/${(await readdir(inFlat))[0]}`
const imgHighContrastPath = `${inHighContrast}/${(await readdir(inHighContrast))[0]}`
if(!imgAnimPath){
// hard-coded matches:
switch(inAnim){
case 'keycap 0': imgAnimPath = animatedList.get("keycap digit zero"); break;
case 'keycap 1': imgAnimPath = animatedList.get("keycap digit one"); break;
case 'keycap 2': imgAnimPath = animatedList.get("keycap digit two"); break;
case 'keycap 3': imgAnimPath = animatedList.get("keycap digit three"); break;
case 'keycap 4': imgAnimPath = animatedList.get("keycap digit four"); break;
case 'keycap 5': imgAnimPath = animatedList.get("keycap digit five"); break;
case 'keycap 6': imgAnimPath = animatedList.get("keycap digit six"); break;
case 'keycap 7': imgAnimPath = animatedList.get("keycap digit seven"); break;
case 'keycap 8': imgAnimPath = animatedList.get("keycap digit eight"); break;
case 'keycap 9': imgAnimPath = animatedList.get("keycap digit nine"); break;
case 'keycap #': imgAnimPath = animatedList.get("keycap number sign"); break;
case 'keycap *': imgAnimPath = animatedList.get("keycap asterisk"); break;
case 'pouting face': imgAnimPath = animatedList.get("enraged face"); break;
case 'melting face': imgAnimPath = sourcesDir + '/melting-face_1fae0.png'; break;
default: console.warn(`could not find animated ${metadata.glyph} ${inAnim}. Defaulting to 3D version: ${img3DPath}`)
}
}
// create file references
const imgAnim = Bun.file(imgAnimPath || img3DPath)
const img3d = Bun.file(img3DPath)
const imgColor = Bun.file(imgColorPath)
const imgFlat = Bun.file(imgFlatPath)
const imgHighContrast = Bun.file(imgHighContrastPath)
// output the files to the output folder
if(!await exists(`${thisEmojiOut}/animated.png`)) await Bun.write(`${thisEmojiOut}/animated.png`, imgAnim)
if(!await exists(`${thisEmojiOut}/3d.png`)) await Bun.write(`${thisEmojiOut}/3d.png`, img3d)
if(!await exists(`${thisEmojiOut}/color.svg`)) await Bun.write(`${thisEmojiOut}/color.svg`, imgColor)
if(!await exists(`${thisEmojiOut}/flat.svg`)) await Bun.write(`${thisEmojiOut}/flat.svg`, imgFlat)
if(!await exists(`${thisEmojiOut}/high-contrast.svg`)) await Bun.write(`${thisEmojiOut}/high-contrast.svg`, imgHighContrast)
// create the css rule ('{file}' to be replaced next)
const cssRule = `[data-emoji|="${metadata.glyph}"]{ content: url(${rootURL}/emoji/${thisEmojiDir}/{file}); }`
// replace '{file}' with the appropriate file name
cssRules.animated.push(cssRule.replace("{file}", "animated.png"))
cssRules.threeD.push(cssRule.replace("{file}", "3d.png"))
cssRules.color.push(cssRule.replace("{file}", "color.svg"))
cssRules.flat.push(cssRule.replace("{file}", "flat.svg"))
cssRules.highContrast.push(cssRule.replace("{file}", "high-contrast.svg"))
cssRules.highContrastMask.push(`[data-emoji|="${metadata.glyph}"]{ mask-size: cover; background-color: var(--fluentui-high-contrast-color); mask-image: url(${rootURL}/emoji/${thisEmojiDir}/high-contrast.svg); }`)
htmlTags.push(`<figure><i data-emoji="${metadata.glyph}" title="${metadata.tts}" loading="lazy"></i><figcaption>${metadata.glyph} ${folder}<br><textarea><i data-emoji="${metadata.glyph}" title="${metadata.tts}"></i></textarea></figcaption></figure>`)
}))
// create file references for the css files
const cssAnim = Bun.file(cssDir + '/animated.css')
const css3d = Bun.file(cssDir + '/3d.css')
const cssColor = Bun.file(cssDir + '/color.css')
const cssFlat = Bun.file(cssDir + '/flat.css')
const cssHighContrast = Bun.file(cssDir + '/high-contrast.css')
const cssHighContrastMask = Bun.file(cssDir + '/high-contrast-mask.css')
// output the css files
await Bun.write(cssAnim, cssRules.animated.join('\n'))
await Bun.write(css3d, cssRules.threeD.join('\n'))
await Bun.write(cssColor, cssRules.color.join('\n'))
await Bun.write(cssFlat, cssRules.flat.join('\n'))
await Bun.write(cssHighContrast, cssRules.highContrast.join('\n'))
await Bun.write(cssHighContrastMask, `:root{--fluentui-high-contrast-color:#212121}\n${cssRules.highContrastMask.join('\n')}`)
// do the same for the font
const fontSrc = Bun.file(sourcesDir + '/seguiemj.ttf')
const fontDst = Bun.file(distDir + '/seguiemj.ttf')
const cssFont = Bun.file(cssDir + '/SegoeUIEmoji.css')
await Bun.write(fontDst, fontSrc)
await Bun.write(cssFont, /*css*/`@font-face{font-family:SegoeUIEmoji;src:url(/seguiemj.ttf);}`)
const templateHtml = Bun.file('./index.html')
const html = (await templateHtml.text()).replace('{{content}}', htmlTags.join('\n '))
const htmlFile = Bun.file(distDir + '/index.html')
await Bun.write(htmlFile, html)