Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve emoji-picker #5515

Merged
merged 4 commits into from
Oct 20, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}

}
12 changes: 10 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">
<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">
mei23 marked this conversation as resolved.
Show resolved Hide resolved
<span>{{ $t('add-emoji.category') }}</span>
</ui-input>
<ui-input v-model="emoji.aliases">
<span>{{ $t('add-emoji.aliases') }}</span>
</ui-input>
Expand Down Expand Up @@ -61,6 +67,7 @@ export default Vue.extend({
data() {
return {
name: '',
category: '',
url: '',
aliases: '',
emojis: [],
Expand All @@ -76,6 +83,7 @@ export default Vue.extend({
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 +102,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 +113,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) {
acid-chicken marked this conversation as resolved.
Show resolved Hide resolved
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
15 changes: 14 additions & 1 deletion src/server/api/endpoints/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,19 @@ export const meta = {
export default define(meta, async (ps, me) => {
const instance = await fetchMeta(true);

const emojis = await Emojis.find({ where: { host: null }, cache: { id: 'meta_emojis', milliseconds: 3600000 } }); // 1 hour
const emojis = await Emojis.find({
where: {
host: null
},
order: {
category: 'ASC',
name: 'ASC'
},
cache: {
id: 'meta_emojis',
milliseconds: 3600000 // 1 hour
}
});

const response: any = {
maintainerName: instance.maintainerName,
Expand Down Expand Up @@ -144,6 +156,7 @@ export default define(meta, async (ps, me) => {
id: e.id,
aliases: e.aliases,
name: e.name,
category: e.category,
url: e.url,
})),
enableEmail: instance.enableEmail,
Expand Down