chore(client): rendering performance tweak a bit

This commit is contained in:
syuilo 2022-07-05 23:01:23 +09:00
parent f882e0b6b6
commit bc73ad2e56
5 changed files with 85 additions and 84 deletions

View file

@ -1,15 +1,17 @@
<template>
<!-- このコンポーネントの要素のclassは親から利用されるのでむやみに弄らないこと -->
<section>
<header class="_acrylic" @click="shown = !shown">
<i class="toggle fa-fw" :class="shown ? 'fas fa-chevron-down' : 'fas fa-chevron-up'"></i> <slot></slot> ({{ emojis.length }})
</header>
<div v-if="shown">
<button v-for="emoji in emojis"
<div v-if="shown" class="body">
<button
v-for="emoji in emojis"
:key="emoji"
class="_button"
class="_button item"
@click="emit('chosen', emoji, $event)"
>
<MkEmoji :emoji="emoji" :normal="true"/>
<MkEmoji class="emoji" :emoji="emoji" :normal="true"/>
</button>
</div>
</section>

View file

@ -3,63 +3,67 @@
<input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @paste.stop="paste" @keyup.enter="done()">
<div ref="emojis" class="emojis">
<section class="result">
<div v-if="searchResultCustom.length > 0">
<button v-for="emoji in searchResultCustom"
<div v-if="searchResultCustom.length > 0" class="body">
<button
v-for="emoji in searchResultCustom"
:key="emoji.id"
class="_button"
class="_button item"
:title="emoji.name"
tabindex="0"
@click="chosen(emoji, $event)"
>
<!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>-->
<img :src="disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
<img class="emoji" :src="disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url"/>
</button>
</div>
<div v-if="searchResultUnicode.length > 0">
<button v-for="emoji in searchResultUnicode"
<div v-if="searchResultUnicode.length > 0" class="body">
<button
v-for="emoji in searchResultUnicode"
:key="emoji.name"
class="_button"
class="_button item"
:title="emoji.name"
tabindex="0"
@click="chosen(emoji, $event)"
>
<MkEmoji :emoji="emoji.char"/>
<MkEmoji class="emoji" :emoji="emoji.char"/>
</button>
</div>
</section>
<div v-if="tab === 'index'" class="index">
<div v-if="tab === 'index'" class="group index">
<section v-if="showPinned">
<div>
<button v-for="emoji in pinned"
<div class="body">
<button
v-for="emoji in pinned"
:key="emoji"
class="_button"
class="_button item"
tabindex="0"
@click="chosen(emoji, $event)"
>
<MkEmoji :emoji="emoji" :normal="true"/>
<MkEmoji class="emoji" :emoji="emoji" :normal="true"/>
</button>
</div>
</section>
<section>
<header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.ts.recentUsed }}</header>
<div>
<button v-for="emoji in recentlyUsedEmojis"
<div class="body">
<button
v-for="emoji in recentlyUsedEmojis"
:key="emoji"
class="_button"
class="_button item"
@click="chosen(emoji, $event)"
>
<MkEmoji :emoji="emoji" :normal="true"/>
<MkEmoji class="emoji" :emoji="emoji" :normal="true"/>
</button>
</div>
</section>
</div>
<div>
<div class="group">
<header class="_acrylic">{{ i18n.ts.customEmojis }}</header>
<XSection v-for="category in customEmojiCategories" :key="'custom:' + category" :initial-shown="false" :emojis="customEmojis.filter(e => e.category === category).map(e => ':' + e.name + ':')" @chosen="chosen">{{ category || i18n.ts.other }}</XSection>
</div>
<div>
<div class="group">
<header class="_acrylic">{{ i18n.ts.emoji }}</header>
<XSection v-for="category in categories" :key="category" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection>
</div>
@ -76,6 +80,7 @@
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import XSection from './emoji-picker.section.vue';
import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist';
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import Ripple from '@/components/ripple.vue';
@ -83,7 +88,6 @@ import * as os from '@/os';
import { isTouchUsing } from '@/scripts/touch';
import { deviceKind } from '@/scripts/device-kind';
import { emojiCategories, instance } from '@/instance';
import XSection from './emoji-picker.section.vue';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
@ -266,7 +270,7 @@ watch(q, () => {
function focus() {
if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
search.value?.focus({
preventScroll: true
preventScroll: true,
});
}
}
@ -415,19 +419,16 @@ defineExpose({
font-size: 15px;
}
> div {
> .body {
display: grid;
grid-template-columns: var(--columns);
font-size: 30px;
> button {
> .item {
aspect-ratio: 1 / 1;
width: auto;
height: auto;
min-width: 0;
> * {
font-size: 30px;
}
}
}
}
@ -478,7 +479,7 @@ defineExpose({
display: none;
}
> div {
> .group {
&:not(.index) {
padding: 4px 0 8px 0;
border-top: solid 0.5px var(--divider);
@ -513,16 +514,18 @@ defineExpose({
}
}
> div {
> .body {
position: relative;
padding: $pad;
> button {
> .item {
position: relative;
padding: 0;
width: var(--eachSize);
height: var(--eachSize);
contain: strict;
border-radius: 4px;
font-size: 24px;
&:focus-visible {
outline: solid 2px var(--focus);
@ -538,8 +541,7 @@ defineExpose({
box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
}
> * {
font-size: 24px;
> .emoji {
height: 1.25em;
vertical-align: -.25em;
pointer-events: none;

View file

@ -2,9 +2,9 @@
<div v-if="hide" class="qjewsnkg" @click="hide = false">
<ImgWithBlurhash class="bg" :hash="image.blurhash" :title="image.comment" :alt="image.comment"/>
<div class="text">
<div>
<b><i class="fas fa-exclamation-triangle"></i> {{ $ts.sensitive }}</b>
<span>{{ $ts.clickToShow }}</span>
<div class="wrapper">
<b style="display: block;"><i class="fas fa-exclamation-triangle"></i> {{ $ts.sensitive }}</b>
<span style="display: block;">{{ $ts.clickToShow }}</span>
</div>
</div>
</div>
@ -37,8 +37,8 @@ let hide = $ref(true);
const url = (props.raw || defaultStore.state.loadRawImages)
? props.image.url
: defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl(props.image.thumbnailUrl)
: props.image.thumbnailUrl;
? getStaticImageUrl(props.image.thumbnailUrl)
: props.image.thumbnailUrl;
// Plugin:register_note_view_interruptor 使watch
watch(() => props.image, () => {
@ -68,15 +68,11 @@ watch(() => props.image, () => {
justify-content: center;
align-items: center;
> div {
> .wrapper {
display: table-cell;
text-align: center;
font-size: 0.8em;
color: #fff;
> * {
display: block;
}
}
}
}

View file

@ -2,7 +2,7 @@
<div v-show="files.length != 0" class="skeikyzd">
<XDraggable v-model="_files" class="files" item-key="id" animation="150" delay="100" delay-on-touch-only="true">
<template #item="{element}">
<div @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)">
<div class="file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)">
<MkDriveFileThumbnail :data-id="element.id" class="thumbnail" :file="element" fit="cover"/>
<div v-if="element.isSensitive" class="sensitive">
<i class="fas fa-exclamation-triangle icon"></i>
@ -22,18 +22,18 @@ import * as os from '@/os';
export default defineComponent({
components: {
XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
MkDriveFileThumbnail
MkDriveFileThumbnail,
},
props: {
files: {
type: Array,
required: true
required: true,
},
detachMediaFn: {
type: Function,
required: false
}
required: false,
},
},
emits: ['updated', 'detach', 'changeSensitive', 'changeName'],
@ -51,8 +51,8 @@ export default defineComponent({
},
set(value) {
this.$emit('updated', value);
}
}
},
},
},
methods: {
@ -66,7 +66,7 @@ export default defineComponent({
toggleSensitive(file) {
os.api('drive/files/update', {
fileId: file.id,
isSensitive: !file.isSensitive
isSensitive: !file.isSensitive,
}).then(() => {
this.$emit('changeSensitive', file, !file.isSensitive);
});
@ -75,12 +75,12 @@ export default defineComponent({
const { canceled, result } = await os.inputText({
title: this.$ts.enterFileName,
default: file.name,
allowEmpty: false
allowEmpty: false,
});
if (canceled) return;
os.api('drive/files/update', {
fileId: file.id,
name: result
name: result,
}).then(() => {
this.$emit('changeName', file, result);
file.name = result;
@ -88,13 +88,13 @@ export default defineComponent({
},
async describe(file) {
os.popup(defineAsyncComponent(() => import("@/components/media-caption.vue")), {
os.popup(defineAsyncComponent(() => import('@/components/media-caption.vue')), {
title: this.$ts.describeFile,
input: {
placeholder: this.$ts.inputNewDescription,
default: file.comment !== null ? file.comment : "",
default: file.comment !== null ? file.comment : '',
},
image: file
image: file,
}, {
done: result => {
if (!result || result.canceled) return;
@ -105,7 +105,7 @@ export default defineComponent({
}).then(() => {
file.comment = comment;
});
}
},
}, 'closed');
},
@ -114,22 +114,22 @@ export default defineComponent({
this.menu = os.popupMenu([{
text: this.$ts.renameFile,
icon: 'fas fa-i-cursor',
action: () => { this.rename(file); }
action: () => { this.rename(file); },
}, {
text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,
icon: file.isSensitive ? 'fas fa-eye-slash' : 'fas fa-eye',
action: () => { this.toggleSensitive(file); }
action: () => { this.toggleSensitive(file); },
}, {
text: this.$ts.describeFile,
icon: 'fas fa-i-cursor',
action: () => { this.describe(file); }
action: () => { this.describe(file); },
}, {
text: this.$ts.attachCancel,
icon: 'fas fa-times-circle',
action: () => { this.detachMedia(file.id); }
action: () => { this.detachMedia(file.id); },
}], ev.currentTarget ?? ev.target).then(() => this.menu = null);
}
}
},
},
});
</script>
@ -142,7 +142,7 @@ export default defineComponent({
display: flex;
flex-wrap: wrap;
> div {
> .file {
position: relative;
width: 64px;
height: 64px;

View file

@ -1,5 +1,6 @@
<template>
<div v-size="{ max: [310, 500] }" class="gafaadew"
<div
v-size="{ max: [310, 500] }" class="gafaadew"
:class="{ modal, _popup: modal }"
@dragover.stop="onDragover"
@dragenter="onDragenter"
@ -11,7 +12,7 @@
<button v-click-anime v-tooltip="i18n.ts.switchAccount" class="account _button" @click="openAccountMenu">
<MkAvatar :user="postAccount ?? $i" class="avatar"/>
</button>
<div>
<div class="right">
<span class="text-count" :class="{ over: textLength > maxTextLength }">{{ maxTextLength - textLength }}</span>
<span v-if="localOnly" class="local-only"><i class="fas fa-biohazard"></i></span>
<button ref="visibilityButton" v-tooltip="i18n.ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility">
@ -68,6 +69,8 @@ import * as misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor';
import { length } from 'stringz';
import { toASCII } from 'punycode/';
import * as Acct from 'misskey-js/built/acct';
import { throttle } from 'throttle-debounce';
import XNoteSimple from './note-simple.vue';
import XNotePreview from './note-preview.vue';
import XPostFormAttaches from './post-form-attaches.vue';
@ -75,14 +78,12 @@ import XPollEditor from './poll-editor.vue';
import { host, url } from '@/config';
import { erase, unique } from '@/scripts/array';
import { extractMentions } from '@/scripts/extract-mentions';
import * as Acct from 'misskey-js/built/acct';
import { formatTimeString } from '@/scripts/format-time-string';
import { Autocomplete } from '@/scripts/autocomplete';
import * as os from '@/os';
import { stream } from '@/stream';
import { selectFiles } from '@/scripts/select-file';
import { defaultStore, notePostInterruptors, postFormActions } from '@/store';
import { throttle } from 'throttle-debounce';
import MkInfo from '@/components/ui/info.vue';
import { i18n } from '@/i18n';
import { instance } from '@/instance';
@ -181,7 +182,7 @@ const placeholder = $computed((): string => {
i18n.ts._postForm._placeholders.c,
i18n.ts._postForm._placeholders.d,
i18n.ts._postForm._placeholders.e,
i18n.ts._postForm._placeholders.f
i18n.ts._postForm._placeholders.f,
];
return xs[Math.floor(Math.random() * xs.length)];
}
@ -238,10 +239,10 @@ if (props.reply && props.reply.text != null) {
for (const x of extractMentions(ast)) {
const mention = x.host ?
`@${x.username}@${toASCII(x.host)}` :
(otherHost == null || otherHost === host) ?
`@${x.username}` :
`@${x.username}@${toASCII(otherHost)}`;
`@${x.username}@${toASCII(x.host)}` :
(otherHost == null || otherHost === host) ?
`@${x.username}` :
`@${x.username}@${toASCII(otherHost)}`;
//
if ($i.username === x.username && (x.host == null || x.host === host)) continue;
@ -263,7 +264,7 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib
visibility = props.reply.visibility;
if (props.reply.visibility === 'specified') {
os.api('users/show', {
userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId)
userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId),
}).then(users => {
users.forEach(pushVisibleUser);
});
@ -399,7 +400,7 @@ function setVisibility() {
if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set('localOnly', localOnly);
}
}
},
}, 'closed');
}
@ -522,8 +523,8 @@ function saveDraft() {
visibility: visibility,
localOnly: localOnly,
files: files,
poll: poll
}
poll: poll,
},
};
localStorage.setItem('drafts', JSON.stringify(draftData));
@ -612,11 +613,11 @@ function showActions(ev) {
text: action.title,
action: () => {
action.handler({
text: text
text: text,
}, (key, value) => {
if (key === 'text') { text = value; }
});
}
},
})), ev.currentTarget ?? ev.target);
}
@ -726,7 +727,7 @@ onMounted(() => {
}
}
> div {
> .right {
position: absolute;
top: 0;
right: 0;
@ -924,7 +925,7 @@ onMounted(() => {
line-height: 50px;
}
> div {
> .right {
> .text-count {
line-height: 50px;
}