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 = new Map() 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(`
${metadata.glyph} ${folder}
`) })) // 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)