Finished up and added a landing page for testing

Also added a melting-face animated emoji I found
This commit is contained in:
Gordon Pedersen 2024-05-20 17:05:38 +10:00
parent c6a43654b2
commit d058278cb2
6 changed files with 1438 additions and 19 deletions

60
index.html Normal file
View file

@ -0,0 +1,60 @@
<html>
<head>
<title>FluentUI Emoji</title>
<link rel="stylesheet" id="emoji-css" href="/css/animated.css">
<style>
body {display: flex; flex-flow: row wrap; justify-content:space-evenly; gap: 20px; background-color: #fff; color: #000; font-size: 18px; font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}
body{ --fluentui-high-contrast-color:#0088FF; }
figure {text-align: center;}
figure>:not(figcaption) { width: 256px; height: 256px; display:block; }
textarea {width: 100%; border-color: #000;}
#style-selector {flex-basis: 100%; text-align: center;}
#style-selector select { font-size: 25px; border-radius: 8px; padding: 10px; margin: 4px; border-color:#000; }
#style-selector textarea {text-align: center; padding:8px;}
[loading="lazy"]{content:none; mask-image: none;}
@media (prefers-color-scheme: dark) {
body, textarea, #style-selector select { background-color: #000; color: #fff; border-color: #fff; }
}
</style>
</head>
<body>
<div id="style-selector">
<select>
<option value="/css/animated.css">Animated</option>
<option value="/css/3d.css">3D</option>
<option value="/css/color.css">Color</option>
<option value="/css/flat.css">Flat</option>
<option value="/css/high-contrast.css">High Contrast</option>
<option value="/css/high-contrast-mask.css">High Contrast (as mask)</option>
</select>
<textarea><link rel="stylesheet" href="/css/animated.css"></textarea>
</div>
{{content}}
</body>
<script type="text/javascript">
// functionality for style selector dropdown and lazily loading images
const imageObserver = new IntersectionObserver((entries, observer) => entries.forEach(function(entry) {
if (entry.isIntersecting) {
entry.target.setAttribute('loading', 'loaded')
imageObserver.unobserve(entry.target)
}
}))
function setCss(path) {
document.querySelectorAll('[loading]:not([loading="lazy"])').forEach(e => {
e.setAttribute('loading', 'lazy')
imageObserver.observe(e)
})
document.querySelector('#style-selector textarea').value = `<link rel="stylesheet" href="${path.startsWith('/') ? new URL(window.location.href).origin : ''}${path}">`
if(path.endsWith('high-contrast-mask.css')) {
document.querySelector('#style-selector textarea').value +=`\n<style>html{--fluentui-high-contrast-color:#0088FF; /* your color here */}</style>`
}
document.getElementById('emoji-css').href = path
}
document.addEventListener("DOMContentLoaded", function() {
document.querySelectorAll('[loading="lazy"]').forEach(image => imageObserver.observe(image))
document.querySelector('#style-selector select').addEventListener('change', (ev) => setCss(ev.target.value))
setCss(document.getElementById('emoji-css').href)
})
</script>
</html>

View file

@ -1,15 +1,22 @@
import { mkdir, exists, readdir, copyFile } from "node:fs/promises" import { mkdir, exists, readdir } from "node:fs/promises"
import * as path from 'path' import * as path from 'path'
const emojiDir = './emoji' // 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) if(!await exists(emojiDir)) await mkdir(emojiDir)
const cssDir = './css' const cssDir = distDir + '/css'
if(!await exists(cssDir)) await mkdir(cssDir) if(!await exists(cssDir)) await mkdir(cssDir)
const fluentDir = './sources/fluentui-emoji/assets' // setup input dirs
const fluentDir = (process.env.FLUENTUI_EMOJI_DIR || './sources/fluentui-emoji') + '/assets'
const fluentList = await readdir(fluentDir) const fluentList = await readdir(fluentDir)
const animatedDir = `./sources/Animated-Fluent-Emojis/Emojis` const animatedDir = (process.env.ANIMATED_FLUENT_EMOJIS_DIR || './sources/Animated-Fluent-Emojis') + '/Emojis'
const animatedCatList = await readdir(animatedDir) 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>() const animatedList:Map<string, string> = new Map<string, string>()
for(let c in animatedCatList) { for(let c in animatedCatList) {
const category = animatedCatList[c] const category = animatedCatList[c]
@ -20,22 +27,32 @@ for(let c in animatedCatList) {
animatedList.set(key, `${catDir}/${filename}`) animatedList.set(key, `${catDir}/${filename}`)
}) })
} }
const rootURL = process.env.ROOT_URL || "https://flumoji.pages.dev/emoji/"
// set up the root url for the css rules
const rootURL = process.env.ROOT_URL || "https://flumoji.pages.dev"
const cssRules = { const cssRules = {
animated: [] as string[], animated: [] as string[],
threeD: [] as string[], threeD: [] as string[],
color: [] as string[], color: [] as string[],
flat: [] as string[], flat: [] as string[],
highContrast: [] 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) => { await Promise.all(fluentList.map(async (folder) => {
// gather the necessary information
let thisEmojiIn = `${fluentDir}/${folder}` let thisEmojiIn = `${fluentDir}/${folder}`
const metadataFile = Bun.file(`${thisEmojiIn}/metadata.json`) const metadataFile = Bun.file(`${thisEmojiIn}/metadata.json`)
const metadata = await metadataFile.json() const metadata = await metadataFile.json()
const thisEmojiOut = `${emojiDir}/${metadata.glyph}` const thisEmojiDir = metadata.unicode.replaceAll(' ', '-')
const thisEmojiOut = `${emojiDir}/${thisEmojiDir}`
// create the output directory
if(!await exists(thisEmojiOut)) await mkdir(thisEmojiOut) 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) 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` if(metadata.unicodeSkintones) thisEmojiIn = `${thisEmojiIn}/Default`
const in3d = `${thisEmojiIn}/3D` const in3d = `${thisEmojiIn}/3D`
@ -44,10 +61,10 @@ await Promise.all(fluentList.map(async (folder) => {
const inHighContrast = `${thisEmojiIn}/High Contrast` const inHighContrast = `${thisEmojiIn}/High Contrast`
// we have to search for the appropriate animated folder // we have to search for the appropriate animated folder
let title = metadata.tts.toLowerCase().replace(/[:]/g, '') const inAnim = metadata.tts.toLowerCase().replace(/[:]/g, '')
// if(metadata.unicodeSkintones) title += ' Light Skin Tone'
let imgAnimPath = animatedList.get(title) // get the correct image file
let imgAnimPath = animatedList.get(inAnim)
const img3DPath = `${in3d}/${(await readdir(in3d))[0]}` const img3DPath = `${in3d}/${(await readdir(in3d))[0]}`
const imgColorPath = `${inColor}/${(await readdir(inColor))[0]}` const imgColorPath = `${inColor}/${(await readdir(inColor))[0]}`
const imgFlatPath = `${inFlat}/${(await readdir(inFlat))[0]}` const imgFlatPath = `${inFlat}/${(await readdir(inFlat))[0]}`
@ -55,7 +72,7 @@ await Promise.all(fluentList.map(async (folder) => {
if(!imgAnimPath){ if(!imgAnimPath){
// hard-coded matches: // hard-coded matches:
switch(title){ switch(inAnim){
case 'keycap 0': imgAnimPath = animatedList.get("keycap digit zero"); break; case 'keycap 0': imgAnimPath = animatedList.get("keycap digit zero"); break;
case 'keycap 1': imgAnimPath = animatedList.get("keycap digit one"); break; case 'keycap 1': imgAnimPath = animatedList.get("keycap digit one"); break;
case 'keycap 2': imgAnimPath = animatedList.get("keycap digit two"); break; case 'keycap 2': imgAnimPath = animatedList.get("keycap digit two"); break;
@ -69,41 +86,57 @@ await Promise.all(fluentList.map(async (folder) => {
case 'keycap #': imgAnimPath = animatedList.get("keycap number sign"); break; case 'keycap #': imgAnimPath = animatedList.get("keycap number sign"); break;
case 'keycap *': imgAnimPath = animatedList.get("keycap asterisk"); break; case 'keycap *': imgAnimPath = animatedList.get("keycap asterisk"); break;
case 'pouting face': imgAnimPath = animatedList.get("enraged face"); break; case 'pouting face': imgAnimPath = animatedList.get("enraged face"); break;
default: console.warn(`could not find animated ${metadata.glyph} ${title}. Defaulting to 3D version: ${img3DPath}`) case 'melting face': imgAnimPath = './sources/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 imgAnim = Bun.file(imgAnimPath || img3DPath)
const img3d = Bun.file(img3DPath) const img3d = Bun.file(img3DPath)
const imgColor = Bun.file(imgColorPath) const imgColor = Bun.file(imgColorPath)
const imgFlat = Bun.file(imgFlatPath) const imgFlat = Bun.file(imgFlatPath)
const imgHighContrast = Bun.file(imgHighContrastPath) 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}/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}/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}/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}/flat.svg`)) await Bun.write(`${thisEmojiOut}/flat.svg`, imgFlat)
if(!await exists(`${thisEmojiOut}/high-contrast.svg`)) await Bun.write(`${thisEmojiOut}/high-contrast.svg`, imgHighContrast) if(!await exists(`${thisEmojiOut}/high-contrast.svg`)) await Bun.write(`${thisEmojiOut}/high-contrast.svg`, imgHighContrast)
const cssRule = `[alt|="${metadata.glyph}"]{ content: url(${rootURL}${metadata.glyph}/{file}); }` // 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.animated.push(cssRule.replace("{file}", "animated.png"))
cssRules.threeD.push(cssRule.replace("{file}", "3d.png")) cssRules.threeD.push(cssRule.replace("{file}", "3d.png"))
cssRules.color.push(cssRule.replace("{file}", "color.svg")) cssRules.color.push(cssRule.replace("{file}", "color.svg"))
cssRules.flat.push(cssRule.replace("{file}", "flat.svg")) cssRules.flat.push(cssRule.replace("{file}", "flat.svg"))
cssRules.highContrast.push(cssRule.replace("{file}", "high-contrast.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(path.join(cssDir, 'animated.css')) const cssAnim = Bun.file(path.join(cssDir, 'animated.css'))
const css3d = Bun.file(path.join(cssDir, '3d.css')) const css3d = Bun.file(path.join(cssDir, '3d.css'))
const cssColor = Bun.file(path.join(cssDir, 'color.css')) const cssColor = Bun.file(path.join(cssDir, 'color.css'))
const cssFlat = Bun.file(path.join(cssDir, 'flat.css')) const cssFlat = Bun.file(path.join(cssDir, 'flat.css'))
const cssHighContrast = Bun.file(path.join(cssDir, 'high-contrast.css')) const cssHighContrast = Bun.file(path.join(cssDir, 'high-contrast.css'))
const cssHighContrastMask = Bun.file(path.join(cssDir, 'high-contrast-mask.css'))
// output the css files
await Bun.write(cssAnim, cssRules.animated.join('\n')) await Bun.write(cssAnim, cssRules.animated.join('\n'))
await Bun.write(css3d, cssRules.threeD.join('\n')) await Bun.write(css3d, cssRules.threeD.join('\n'))
await Bun.write(cssColor, cssRules.color.join('\n')) await Bun.write(cssColor, cssRules.color.join('\n'))
await Bun.write(cssFlat, cssRules.flat.join('\n')) await Bun.write(cssFlat, cssRules.flat.join('\n'))
await Bun.write(cssHighContrast, cssRules.highContrast.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')}`)
const templateHtml = Bun.file('./index.html')
const html = (await templateHtml.text()).replace('{{content}}', htmlTags.join('\n '))
const htmlFile = Bun.file(path.join(distDir, 'index.html'))
await Bun.write(htmlFile, html)

1318
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -3,9 +3,10 @@
"module": "index.ts", "module": "index.ts",
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@types/bun": "latest" "@types/bun": "latest",
"wrangler": "^3.57.0"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.0.0" "typescript": "^5.0.0"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

7
wrangler.toml Normal file
View file

@ -0,0 +1,7 @@
# Generated by Wrangler on Mon May 20 2024 11:59:58 GMT+1000 (Australian Eastern Standard Time)
name = "flumoji"
compatibility_date = "2024-05-16"
pages_build_output_dir = "dist"
[env.production]
compatibility_date = "2024-05-16"