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> <template>
<!-- このコンポーネントの要素のclassは親から利用されるのでむやみに弄らないこと -->
<section> <section>
<header class="_acrylic" @click="shown = !shown"> <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 }}) <i class="toggle fa-fw" :class="shown ? 'fas fa-chevron-down' : 'fas fa-chevron-up'"></i> <slot></slot> ({{ emojis.length }})
</header> </header>
<div v-if="shown"> <div v-if="shown" class="body">
<button v-for="emoji in emojis" <button
v-for="emoji in emojis"
:key="emoji" :key="emoji"
class="_button" class="_button item"
@click="emit('chosen', emoji, $event)" @click="emit('chosen', emoji, $event)"
> >
<MkEmoji :emoji="emoji" :normal="true"/> <MkEmoji class="emoji" :emoji="emoji" :normal="true"/>
</button> </button>
</div> </div>
</section> </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()"> <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"> <div ref="emojis" class="emojis">
<section class="result"> <section class="result">
<div v-if="searchResultCustom.length > 0"> <div v-if="searchResultCustom.length > 0" class="body">
<button v-for="emoji in searchResultCustom" <button
v-for="emoji in searchResultCustom"
:key="emoji.id" :key="emoji.id"
class="_button" class="_button item"
:title="emoji.name" :title="emoji.name"
tabindex="0" tabindex="0"
@click="chosen(emoji, $event)" @click="chosen(emoji, $event)"
> >
<!--<MkEmoji v-if="emoji.char != null" :emoji="emoji.char"/>--> <!--<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> </button>
</div> </div>
<div v-if="searchResultUnicode.length > 0"> <div v-if="searchResultUnicode.length > 0" class="body">
<button v-for="emoji in searchResultUnicode" <button
v-for="emoji in searchResultUnicode"
:key="emoji.name" :key="emoji.name"
class="_button" class="_button item"
:title="emoji.name" :title="emoji.name"
tabindex="0" tabindex="0"
@click="chosen(emoji, $event)" @click="chosen(emoji, $event)"
> >
<MkEmoji :emoji="emoji.char"/> <MkEmoji class="emoji" :emoji="emoji.char"/>
</button> </button>
</div> </div>
</section> </section>
<div v-if="tab === 'index'" class="index"> <div v-if="tab === 'index'" class="group index">
<section v-if="showPinned"> <section v-if="showPinned">
<div> <div class="body">
<button v-for="emoji in pinned" <button
v-for="emoji in pinned"
:key="emoji" :key="emoji"
class="_button" class="_button item"
tabindex="0" tabindex="0"
@click="chosen(emoji, $event)" @click="chosen(emoji, $event)"
> >
<MkEmoji :emoji="emoji" :normal="true"/> <MkEmoji class="emoji" :emoji="emoji" :normal="true"/>
</button> </button>
</div> </div>
</section> </section>
<section> <section>
<header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.ts.recentUsed }}</header> <header class="_acrylic"><i class="far fa-clock fa-fw"></i> {{ i18n.ts.recentUsed }}</header>
<div> <div class="body">
<button v-for="emoji in recentlyUsedEmojis" <button
v-for="emoji in recentlyUsedEmojis"
:key="emoji" :key="emoji"
class="_button" class="_button item"
@click="chosen(emoji, $event)" @click="chosen(emoji, $event)"
> >
<MkEmoji :emoji="emoji" :normal="true"/> <MkEmoji class="emoji" :emoji="emoji" :normal="true"/>
</button> </button>
</div> </div>
</section> </section>
</div> </div>
<div> <div class="group">
<header class="_acrylic">{{ i18n.ts.customEmojis }}</header> <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> <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> <div class="group">
<header class="_acrylic">{{ i18n.ts.emoji }}</header> <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> <XSection v-for="category in categories" :key="category" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection>
</div> </div>
@ -76,6 +80,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'; import { ref, computed, watch, onMounted } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XSection from './emoji-picker.section.vue';
import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist'; import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist';
import { getStaticImageUrl } from '@/scripts/get-static-image-url'; import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import Ripple from '@/components/ripple.vue'; import Ripple from '@/components/ripple.vue';
@ -83,7 +88,6 @@ import * as os from '@/os';
import { isTouchUsing } from '@/scripts/touch'; import { isTouchUsing } from '@/scripts/touch';
import { deviceKind } from '@/scripts/device-kind'; import { deviceKind } from '@/scripts/device-kind';
import { emojiCategories, instance } from '@/instance'; import { emojiCategories, instance } from '@/instance';
import XSection from './emoji-picker.section.vue';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
@ -266,7 +270,7 @@ watch(q, () => {
function focus() { function focus() {
if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) { if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
search.value?.focus({ search.value?.focus({
preventScroll: true preventScroll: true,
}); });
} }
} }
@ -415,19 +419,16 @@ defineExpose({
font-size: 15px; font-size: 15px;
} }
> div { > .body {
display: grid; display: grid;
grid-template-columns: var(--columns); grid-template-columns: var(--columns);
font-size: 30px;
> button { > .item {
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
width: auto; width: auto;
height: auto; height: auto;
min-width: 0; min-width: 0;
> * {
font-size: 30px;
}
} }
} }
} }
@ -478,7 +479,7 @@ defineExpose({
display: none; display: none;
} }
> div { > .group {
&:not(.index) { &:not(.index) {
padding: 4px 0 8px 0; padding: 4px 0 8px 0;
border-top: solid 0.5px var(--divider); border-top: solid 0.5px var(--divider);
@ -513,16 +514,18 @@ defineExpose({
} }
} }
> div { > .body {
position: relative; position: relative;
padding: $pad; padding: $pad;
> button { > .item {
position: relative; position: relative;
padding: 0; padding: 0;
width: var(--eachSize); width: var(--eachSize);
height: var(--eachSize); height: var(--eachSize);
contain: strict;
border-radius: 4px; border-radius: 4px;
font-size: 24px;
&:focus-visible { &:focus-visible {
outline: solid 2px var(--focus); outline: solid 2px var(--focus);
@ -538,8 +541,7 @@ defineExpose({
box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
} }
> * { > .emoji {
font-size: 24px;
height: 1.25em; height: 1.25em;
vertical-align: -.25em; vertical-align: -.25em;
pointer-events: none; pointer-events: none;

View file

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

View file

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

View file

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