Finished up and added a landing page for testing
Also added a melting-face animated emoji I found
This commit is contained in:
parent
c6a43654b2
commit
d058278cb2
6 changed files with 1438 additions and 19 deletions
60
index.html
Normal file
60
index.html
Normal 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>
|
65
index.ts
65
index.ts
|
@ -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
1318
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,8 @@
|
||||||
"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"
|
||||||
|
|
BIN
sources/melting-face_1fae0.png
Normal file
BIN
sources/melting-face_1fae0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 MiB |
7
wrangler.toml
Normal file
7
wrangler.toml
Normal 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"
|
Loading…
Reference in a new issue