微软在发布 Windows 11 系统的时候,发布过一个开源的 Emoji 表情 fluentui-emoji 。因为我经常需要里面的一些表情图片,在仓库一个个查找特别的不方便,所以我做了一个表情查看器,可以很方便的查看所有表情,同时可以定位到表情文件的位置。这套 fluentui-emoji 表情一共有 1545 个。
安装 Electron 环境
- 安装 nodejs。到官网 nodejs.org/en 下载最新的 nodejs,然后安装
- 打开命令行输入
git clone https://github.com/electron/electron-quick-start.git命令克隆 Electron 模板项目,使用模板可以快速搭建应用。 - 然后使用
cd electron-quick-start目录进入到目录,接着运行npm install命令还原项目。 - 使用 vscode 打开文件夹,项目文件如下
数据解析
fluentui-emoji 下的 assets 文件夹下的每一个子文件夹对应一个 Emoji 表情文件夹,表情文件夹里面的 metadata.json 文件储存着 Emoji 表情的元数据。3D 文件夹里面储存的是 256x256 的 png 图片,其他文件夹储存的是 svg 矢量图片。然后要做的就是遍历每一个文件夹,解析里面的元数据和图片文件
-
资产文件夹结构
-
Emoji 表情结构
-
metadata.json文件结构
{
"cldr": "zany face",
"fromVersion": "5.0",
"glyph": "🤪",
"glyphAsUtfInEmoticons": [
"1f92a_zanyface",
"hysterical"
],
"group": "Smileys & Emotion",
"keywords": [
"eye",
"goofy",
"large",
"small",
"zany face"
],
"mappedToEmoticons": [
"1f92a_zanyface"
],
"tts": "zany face",
"unicode": "1f92a"
}
- 解析元数据
function loadData(assetPath) {
const dirs = fs.readdirSync(assetPath)
const data = []
const groupData = {}
for (const dir of dirs) {
const fullPath = path.resolve(assetPath, dir)
const metadata = require(path.resolve(fullPath, 'metadata.json'))
let previewImage
let imagePaths = [path.resolve(fullPath, '3D'), path.resolve(fullPath, 'Default', '3D')]
for (const imagePath of imagePaths) {
if (fs.existsSync(imagePath)) {
let files = fs.readdirSync(imagePath)
if (files.length === 0)
return
previewImage = path.resolve(imagePath, files[0])
}
}
const { unicode, group } = metadata
const obj = {
metadata,
id: unicode,
name: dir,
previewImage,
}
data.push(obj)
if (!groupData[group])
groupData[group] = []
groupData[group].push(obj)
}
return groupData
}
编写代码
- 修改
index.html文件,界面部分使用了 vue 进行渲染
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="vue.global.js"></script>
<link href="./styles.css" rel="stylesheet">
<title>EmojiViewer</title>
</head>
<body>
<div id="app" class="container">
<ul class="left">
<li v-for="(item, key) in categories" :class="{active: item.isActive}" @click="catetoryItemClick(item)">{{ key }}</li>
</ul>
<ul class="main" ref="mainElement">
<li v-for="emoji in emojis" :class="{active: emoji.isActive}" @click="emojiItemClick(emoji)">
<img :src="emoji.previewImage" alt="">
<p>{{emoji.name}}</p>
</li>
</ul>
<div class="right">
<img :src="selectedEmoji.previewImage">
<p>{{ selectedEmoji.name }}</p>
<button @click="copyEmoji(selectedEmoji)" type="button">Copy Emoji</button>
<button @click="copyImage(selectedEmoji)" type="button">Copy Image</button>
<button @click="openFile(selectedEmoji)" type="button">Open File</button>
</div>
</div>
<script src="./renderer.js"></script>
</body>
</html>
- 在
renderer.js文件中编写页面处理代码
window.addEventListener('DOMContentLoaded', async () => {
const { createApp, ref, onMounted } = Vue
let emojiData = await ipc.getData()
createApp({
setup() {
const mainElement = ref(null)
const categories = ref(emojiData)
const emojis = ref([])
const selectedEmoji = ref({})
function copyEmoji(emoji) {
ipc.ipc('writeText', emoji.metadata.glyph)
}
function copyImage(emoji) {
ipc.ipc('writeImage', emoji.previewImage)
}
function openFile(emoji) {
ipc.ipc('showItemInFolder', emoji.previewImage)
}
let lastSelectedEmojis
function catetoryItemClick(items) {
if (lastSelectedEmojis) {
lastSelectedEmojis.isActive = false
}
items.isActive = true
lastSelectedEmojis = items
// const main = document.querySelector('.main')
mainElement.value.scrollTop = 0
emojis.value = items
}
function emojiItemClick(emoji) {
if (selectedEmoji.value) {
selectedEmoji.value.isActive = false
}
emoji.isActive = true
selectedEmoji.value = emoji
}
onMounted(() => {
catetoryItemClick(emojiData['Activities'])
emojiItemClick(emojiData['Activities'][0])
})
return {
mainElement,
categories,
emojis,
selectedEmoji,
catetoryItemClick,
emojiItemClick,
copyEmoji,
copyImage,
openFile,
}
}
}).mount('#app')
})