Skip to content

Commit

Permalink
Improve emoji-picker (#5515)
Browse files Browse the repository at this point in the history
* Improve emoji-picker

* remove unimplanted translation

* カテゴリのサジェスト

* use unique
  • Loading branch information
mei23 authored and syuilo committed Oct 20, 2019
1 parent 97b6af6 commit 4c6c06c
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 32 deletions.
3 changes: 3 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,9 @@ common/views/components/reaction-picker.vue:
input-reaction-placeholder: "または絵文字を入力"

common/views/components/emoji-picker.vue:
recent-emoji: "最近使った絵文字"
custom-emoji: "カスタム絵文字"
no-category: "カテゴリなし"
people: ""
animals-and-nature: "動物&自然"
food-and-drink: "食べ物&飲み物"
Expand Down Expand Up @@ -1591,6 +1593,7 @@ admin/views/emoji.vue:
title: "絵文字の登録"
name: "絵文字名"
name-desc: "a~z 0~9 _ の文字が使えます。"
category: "カテゴリ"
aliases: "エイリアス"
aliases-desc: "スペースで区切って複数設定できます。"
url: "絵文字画像URL"
Expand Down
13 changes: 13 additions & 0 deletions migration/1571220798684-CustomEmojiCategory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {MigrationInterface, QueryRunner} from "typeorm";

export class CustomEmojiCategory1571220798684 implements MigrationInterface {

public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "emoji" ADD "category" character varying(128)`, undefined);
}

public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "category"`, undefined);
}

}
19 changes: 17 additions & 2 deletions src/client/app/admin/views/emoji.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<span>{{ $t('add-emoji.name') }}</span>
<template #desc>{{ $t('add-emoji.name-desc') }}</template>
</ui-input>
<ui-input v-model="category" :datalist="categoryList">
<span>{{ $t('add-emoji.category') }}</span>
</ui-input>
<ui-input v-model="aliases">
<span>{{ $t('add-emoji.aliases') }}</span>
<template #desc>{{ $t('add-emoji.aliases-desc') }}</template>
Expand All @@ -24,7 +27,7 @@

<ui-card>
<template #title><fa :icon="faGrin"/> {{ $t('emojis.title') }}</template>
<section v-for="emoji in emojis" class="oryfrbft">
<section v-for="emoji in emojis" :key="emoji.name" class="oryfrbft">
<div>
<img :src="emoji.url" :alt="emoji.name" style="width: 64px;"/>
</div>
Expand All @@ -33,6 +36,9 @@
<ui-input v-model="emoji.name">
<span>{{ $t('add-emoji.name') }}</span>
</ui-input>
<ui-input v-model="emoji.category" :datalist="categoryList">
<span>{{ $t('add-emoji.category') }}</span>
</ui-input>
<ui-input v-model="emoji.aliases">
<span>{{ $t('add-emoji.aliases') }}</span>
</ui-input>
Expand All @@ -55,12 +61,14 @@
import Vue from 'vue';
import i18n from '../../i18n';
import { faGrin } from '@fortawesome/free-regular-svg-icons';
import { unique } from '../../../../prelude/array';
export default Vue.extend({
i18n: i18n('admin/views/emoji.vue'),
data() {
return {
name: '',
category: '',
url: '',
aliases: '',
emojis: [],
Expand All @@ -72,10 +80,17 @@ export default Vue.extend({
this.fetchEmojis();
},
computed: {
categoryList() {
return unique(this.emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
}
},
methods: {
add() {
this.$root.api('admin/emoji/add', {
name: this.name,
category: this.category,
url: this.url,
aliases: this.aliases.split(' ').filter(x => x.length > 0)
}).then(() => {
Expand All @@ -94,7 +109,6 @@ export default Vue.extend({
fetchEmojis() {
this.$root.api('admin/emoji/list').then(emojis => {
emojis.reverse();
for (const e of emojis) {
e.aliases = (e.aliases || []).join(' ');
}
Expand All @@ -106,6 +120,7 @@ export default Vue.extend({
this.$root.api('admin/emoji/update', {
id: emoji.id,
name: emoji.name,
category: emoji.category,
url: emoji.url,
aliases: emoji.aliases.split(' ').filter(x => x.length > 0)
}).then(() => {
Expand Down
112 changes: 84 additions & 28 deletions src/client/app/common/views/components/emoji-picker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,46 @@
</button>
</header>
<div class="emojis">
<header><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header>
<div v-if="categories.find(x => x.isActive).name">
<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
:title="emoji.name"
@click="chosen(emoji.char)"
:key="emoji.name"
>
<mk-emoji :emoji="emoji.char"/>
</button>
</div>
<div v-else>
<button v-for="emoji in customEmojis"
:title="emoji.name"
@click="chosen(`:${emoji.name}:`)"
:key="emoji.name"
>
<img :src="emoji.url" :alt="emoji.name"/>
</button>
</div>
<template v-if="categories[0].isActive">
<header class="category"><fa :icon="faHistory" fixed-width/> {{ $t('recent-emoji') }}</header>
<div class="list">
<button v-for="(emoji, i) in ($store.state.device.recentEmojis || [])"
:title="emoji.name"
@click="chosen(emoji)"
:key="i"
>
<mk-emoji v-if="emoji.char != null" :emoji="emoji.char"/>
<img v-else :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
</button>
</div>
</template>

<header class="category"><fa :icon="categories.find(x => x.isActive).icon" fixed-width/> {{ categories.find(x => x.isActive).text }}</header>
<template v-if="categories.find(x => x.isActive).name">
<div class="list">
<button v-for="emoji in emojilist.filter(e => e.category === categories.find(x => x.isActive).name)"
:title="emoji.name"
@click="chosen(emoji)"
:key="emoji.name"
>
<mk-emoji :emoji="emoji.char"/>
</button>
</div>
</template>
<template v-else>
<div v-for="(key, i) in Object.keys(customEmojis)" :key="i">
<header class="sub">{{ key || $t('no-category') }}</header>
<div class="list">
<button v-for="emoji in customEmojis[key]"
:title="emoji.name"
@click="chosen(emoji)"
:key="emoji.name"
>
<img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
</button>
</div>
</div>
</template>
</div>
</div>
</template>
Expand All @@ -38,16 +59,20 @@
import Vue from 'vue';
import i18n from '../../../i18n';
import { emojilist } from '../../../../../misc/emojilist';
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice } from '@fortawesome/free-solid-svg-icons';
import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
import { faAsterisk, faLeaf, faUtensils, faFutbol, faCity, faDice, faGlobe, faHistory } from '@fortawesome/free-solid-svg-icons';
import { faHeart, faFlag } from '@fortawesome/free-regular-svg-icons';
import { groupByX } from '../../../../../prelude/array';
export default Vue.extend({
i18n: i18n('common/views/components/emoji-picker.vue'),
data() {
return {
emojilist,
customEmojis: [],
getStaticImageUrl,
customEmojis: {},
faGlobe, faHistory,
categories: [{
text: this.$t('custom-emoji'),
icon: faAsterisk,
Expand Down Expand Up @@ -97,18 +122,43 @@ export default Vue.extend({
},
created() {
this.customEmojis = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
let local = (this.$root.getMetaSync() || { emojis: [] }).emojis || [];
local = groupByX(local, (x: any) => x.category || '');
this.customEmojis = local;
if (this.$store.state.device.activeEmojiCategoryName) {
this.goCategory(this.$store.state.device.activeEmojiCategoryName);
}
},
methods: {
go(category) {
go(category: any) {
this.goCategory(category.name);
},
goCategory(name: string) {
let matched = false;
for (const c of this.categories) {
c.isActive = c.name === category.name;
c.isActive = c.name === name;
if (c.isActive) {
matched = true;
this.$store.commit('device/set', { key: 'activeEmojiCategoryName', value: c.name });
}
}
if (!matched) {
this.categories[0].isActive = true;
}
},
chosen(emoji) {
this.$emit('chosen', emoji);
chosen(emoji: any) {
const getKey = (emoji: any) => emoji.char || `:${emoji.name}:`;
let recents = this.$store.state.device.recentEmojis || [];
recents = recents.filter((e: any) => getKey(e) !== getKey(emoji));
recents.unshift(emoji)
this.$store.commit('device/set', { key: 'recentEmojis', value: recents.splice(0, 16) });
this.$emit('chosen', getKey(emoji));
}
}
});
Expand Down Expand Up @@ -142,7 +192,7 @@ export default Vue.extend({
overflow-y auto
overflow-x hidden
> header
> header.category
position sticky
top 0
left 0
Expand All @@ -152,7 +202,12 @@ export default Vue.extend({
color var(--text)
font-size 12px
> div
>>> header.sub
padding 4px 8px
color var(--text)
font-size 12px
>>> div.list
display grid
grid-template-columns 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr
gap 4px
Expand Down Expand Up @@ -180,6 +235,7 @@ export default Vue.extend({
left 0
width 100%
height 100%
object-fit contain
font-size 28px
transition transform 0.2s ease
pointer-events none
Expand Down
2 changes: 2 additions & 0 deletions src/client/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ const defaultDeviceSettings = {
enableMobileQuickNotificationView: false,
roomGraphicsQuality: 'medium',
roomUseOrthographicCamera: true,
activeEmojiCategoryName: undefined,
recentEmojis: [],
};

export default (os: MiOS) => new Vuex.Store({
Expand Down
5 changes: 5 additions & 0 deletions src/models/entities/emoji.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export class Emoji {
})
public host: string | null;

@Column('varchar', {
length: 128, nullable: true
})
public category: string | null;

@Column('varchar', {
length: 512,
})
Expand Down
13 changes: 13 additions & 0 deletions src/prelude/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
return groupBy((a, b) => f(a) === f(b), xs);
}

export function groupByX<T>(collections: T[], keySelector: (x: T) => string) {
return collections.reduce((obj: Record<string, T[]>, item: T) => {
const key = keySelector(item);
if (!obj.hasOwnProperty(key)) {
obj[key] = [];
}

obj[key].push(item);

return obj;
}, {});
}

/**
* Compare two arrays by lexicographical order
*/
Expand Down
5 changes: 5 additions & 0 deletions src/server/api/endpoints/admin/emoji/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export const meta = {
validator: $.str.min(1)
},

category: {
validator: $.optional.str
},

aliases: {
validator: $.optional.arr($.str.min(1)),
default: [] as string[]
Expand All @@ -52,6 +56,7 @@ export default define(meta, async (ps, me) => {
id: genId(),
updatedAt: new Date(),
name: ps.name,
category: ps.category,
host: null,
aliases: ps.aliases,
url: ps.url,
Expand Down
9 changes: 8 additions & 1 deletion src/server/api/endpoints/admin/emoji/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,19 @@ export const meta = {

export default define(meta, async (ps) => {
const emojis = await Emojis.find({
host: toPunyNullable(ps.host)
where: {
host: toPunyNullable(ps.host)
},
order: {
category: 'ASC',
name: 'ASC'
}
});

return emojis.map(e => ({
id: e.id,
name: e.name,
category: e.category,
aliases: e.aliases,
host: e.host,
url: e.url
Expand Down
5 changes: 5 additions & 0 deletions src/server/api/endpoints/admin/emoji/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export const meta = {
validator: $.str
},

category: {
validator: $.optional.str
},

url: {
validator: $.str
},
Expand Down Expand Up @@ -53,6 +57,7 @@ export default define(meta, async (ps) => {
await Emojis.update(emoji.id, {
updatedAt: new Date(),
name: ps.name,
category: ps.category,
aliases: ps.aliases,
url: ps.url,
type,
Expand Down
Loading

0 comments on commit 4c6c06c

Please sign in to comment.