Merge pull request 'Add loading spinners to detailed note page & popups(if slow)' (#10139) from Freeplay/calckey:loaders into develop

Reviewed-on: https://codeberg.org/calckey/calckey/pulls/10139
This commit is contained in:
Kainoa Kanter 2023-05-17 01:39:50 +00:00
commit d79968e095
2 changed files with 31 additions and 82 deletions

View file

@ -10,11 +10,13 @@
:class="{ renote: isRenote }" :class="{ renote: isRenote }"
> >
<MkNoteSub <MkNoteSub
v-if="conversation"
v-for="note in conversation" v-for="note in conversation"
:key="note.id" :key="note.id"
class="reply-to" class="reply-to"
:note="note" :note="note"
/> />
<MkLoading v-else-if="appearNote.reply" mini />
<MkNoteSub <MkNoteSub
v-if="appearNote.reply" v-if="appearNote.reply"
:note="appearNote.reply" :note="appearNote.reply"
@ -31,12 +33,14 @@
</div> </div>
<MkNoteSub <MkNoteSub
v-if="directReplies"
v-for="note in directReplies" v-for="note in directReplies"
:key="note.id" :key="note.id"
:note="note" :note="note"
class="reply" class="reply"
:conversation="replies" :conversation="replies"
/> />
<MkLoading v-else-if="appearNote.repliesCount > 0" />
</div> </div>
<div v-else class="_panel muted" @click="muted.muted = false"> <div v-else class="_panel muted" @click="muted.muted = false">
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small"> <I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
@ -66,31 +70,18 @@ import {
reactive, reactive,
ref, ref,
} from "vue"; } from "vue";
import * as mfm from "mfm-js";
import type * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
import MkNote from "@/components/MkNote.vue"; import MkNote from "@/components/MkNote.vue";
import MkNoteSub from "@/components/MkNoteSub.vue"; import MkNoteSub from "@/components/MkNoteSub.vue";
import XNoteSimple from "@/components/MkNoteSimple.vue";
import XReactionsViewer from "@/components/MkReactionsViewer.vue";
import XMediaList from "@/components/MkMediaList.vue";
import XCwButton from "@/components/MkCwButton.vue";
import XPoll from "@/components/MkPoll.vue";
import XStarButton from "@/components/MkStarButton.vue"; import XStarButton from "@/components/MkStarButton.vue";
import XStarButtonNoEmoji from "@/components/MkStarButtonNoEmoji.vue";
import XRenoteButton from "@/components/MkRenoteButton.vue"; import XRenoteButton from "@/components/MkRenoteButton.vue";
import XQuoteButton from "@/components/MkQuoteButton.vue";
import MkUrlPreview from "@/components/MkUrlPreview.vue";
import MkInstanceTicker from "@/components/MkInstanceTicker.vue";
import MkVisibility from "@/components/MkVisibility.vue";
import { pleaseLogin } from "@/scripts/please-login"; import { pleaseLogin } from "@/scripts/please-login";
import { getWordSoftMute } from "@/scripts/check-word-mute"; import { getWordSoftMute } from "@/scripts/check-word-mute";
import { userPage } from "@/filters/user"; import { userPage } from "@/filters/user";
import { notePage } from "@/filters/note";
import { useRouter } from "@/router"; import { useRouter } from "@/router";
import * as os from "@/os"; import * as os from "@/os";
import { defaultStore, noteViewInterruptors } from "@/store"; import { defaultStore, noteViewInterruptors } from "@/store";
import { reactionPicker } from "@/scripts/reaction-picker"; import { reactionPicker } from "@/scripts/reaction-picker";
import { extractUrlFromMfm } from "@/scripts/extract-url-from-mfm";
import { $i } from "@/account"; import { $i } from "@/account";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { getNoteMenu } from "@/scripts/get-note-menu"; import { getNoteMenu } from "@/scripts/get-note-menu";
@ -99,15 +90,11 @@ import { deepClone } from "@/scripts/clone";
import { stream } from "@/stream"; import { stream } from "@/stream";
import { NoteUpdatedEvent } from "calckey-js/built/streaming.types"; import { NoteUpdatedEvent } from "calckey-js/built/streaming.types";
const router = useRouter();
const props = defineProps<{ const props = defineProps<{
note: misskey.entities.Note; note: misskey.entities.Note;
pinned?: boolean; pinned?: boolean;
}>(); }>();
const inChannel = inject("inChannel", null);
let note = $ref(deepClone(props.note)); let note = $ref(deepClone(props.note));
const softMuteReasonI18nSrc = (what?: string) => { const softMuteReasonI18nSrc = (what?: string) => {
@ -120,8 +107,6 @@ const softMuteReasonI18nSrc = (what?: string) => {
return i18n.ts.userSaysSomething; return i18n.ts.userSaysSomething;
}; };
const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
// plugin // plugin
if (noteViewInterruptors.length > 0) { if (noteViewInterruptors.length > 0) {
onMounted(async () => { onMounted(async () => {
@ -155,16 +140,9 @@ const isDeleted = ref(false);
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords)); const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords));
const translation = ref(null); const translation = ref(null);
const translating = ref(false); const translating = ref(false);
const urls = appearNote.text let conversation = $ref<null | misskey.entities.Note[]>([]);
? extractUrlFromMfm(mfm.parse(appearNote.text)).slice(0, 5)
: null;
const showTicker =
defaultStore.state.instanceTicker === "always" ||
(defaultStore.state.instanceTicker === "remote" &&
appearNote.user.instance);
const conversation = ref<misskey.entities.Note[]>([]);
const replies = ref<misskey.entities.Note[]>([]); const replies = ref<misskey.entities.Note[]>([]);
const directReplies = ref<misskey.entities.Note[]>([]); let directReplies = $ref<null | misskey.entities.Note[]>([]);
let isScrolling; let isScrolling;
const keymap = { const keymap = {
@ -260,29 +238,6 @@ function menu(viaKeyboard = false): void {
).then(focus); ).then(focus);
} }
function showRenoteMenu(viaKeyboard = false): void {
if (!isMyRenote) return;
os.popupMenu(
[
{
text: i18n.ts.unrenote,
icon: "ph-trash ph-bold ph-lg",
danger: true,
action: () => {
os.api("notes/delete", {
noteId: note.id,
});
isDeleted.value = true;
},
},
],
renoteTime.value,
{
viaKeyboard: viaKeyboard,
}
);
}
function focus() { function focus() {
noteEl.focus(); noteEl.focus();
} }
@ -291,13 +246,14 @@ function blur() {
noteEl.blur(); noteEl.blur();
} }
directReplies = null;
os.api("notes/children", { os.api("notes/children", {
noteId: appearNote.id, noteId: appearNote.id,
limit: 30, limit: 30,
depth: 12, depth: 12,
}).then((res) => { }).then((res) => {
replies.value = res; replies.value = res;
directReplies.value = res directReplies = res
.filter( .filter(
(note) => (note) =>
note.replyId === appearNote.id || note.replyId === appearNote.id ||
@ -306,12 +262,13 @@ os.api("notes/children", {
.reverse(); .reverse();
}); });
conversation = null;
if (appearNote.replyId) { if (appearNote.replyId) {
os.api("notes/conversation", { os.api("notes/conversation", {
noteId: appearNote.replyId, noteId: appearNote.replyId,
limit: 30, limit: 30,
}).then((res) => { }).then((res) => {
conversation.value = res.reverse(); conversation = res.reverse();
focus(); focus();
}); });
} }
@ -345,7 +302,7 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
replies.value.splice(found, 0, replyNote); replies.value.splice(found, 0, replyNote);
if (found === 0) { if (found === 0) {
directReplies.value.push(replyNote); directReplies.push(replyNote);
} }
break; break;

View file

@ -232,7 +232,7 @@ export async function popup(
export function pageWindow(path: string) { export function pageWindow(path: string) {
popup( popup(
defineAsyncComponent(() => import("@/components/MkPageWindow.vue")), defineAsyncComponent({ loader: () => import("@/components/MkPageWindow.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
initialPath: path, initialPath: path,
}, },
@ -243,7 +243,7 @@ export function pageWindow(path: string) {
export function modalPageWindow(path: string) { export function modalPageWindow(path: string) {
popup( popup(
defineAsyncComponent(() => import("@/components/MkModalPageWindow.vue")), defineAsyncComponent({ loader: () => import("@/components/MkModalPageWindow.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
initialPath: path, initialPath: path,
}, },
@ -313,7 +313,7 @@ export function yesno(props: {
}): Promise<{ canceled: boolean }> { }): Promise<{ canceled: boolean }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent(() => import("@/components/MkDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
...props, ...props,
showCancelButton: true, showCancelButton: true,
@ -344,7 +344,7 @@ export function inputText(props: {
> { > {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent(() => import("@/components/MkDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
title: props.title, title: props.title,
text: props.text, text: props.text,
@ -378,7 +378,7 @@ export function inputParagraph(props: {
> { > {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent(() => import("@/components/MkDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
title: props.title, title: props.title,
text: props.text, text: props.text,
@ -412,7 +412,7 @@ export function inputNumber(props: {
> { > {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent(() => import("@/components/MkDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
title: props.title, title: props.title,
text: props.text, text: props.text,
@ -446,7 +446,7 @@ export function inputDate(props: {
> { > {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent(() => import("@/components/MkDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
title: props.title, title: props.title,
text: props.text, text: props.text,
@ -501,7 +501,7 @@ export function select<C = any>(
> { > {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent(() => import("@/components/MkDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
title: props.title, title: props.title,
text: props.text, text: props.text,
@ -528,7 +528,7 @@ export function success() {
showing.value = false; showing.value = false;
}, 1000); }, 1000);
popup( popup(
defineAsyncComponent(() => import("@/components/MkWaitingDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkWaitingDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
success: true, success: true,
showing: showing, showing: showing,
@ -545,7 +545,7 @@ export function waiting() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const showing = ref(true); const showing = ref(true);
popup( popup(
defineAsyncComponent(() => import("@/components/MkWaitingDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkWaitingDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
success: false, success: false,
showing: showing, showing: showing,
@ -561,7 +561,7 @@ export function waiting() {
export function form(title, form) { export function form(title, form) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent(() => import("@/components/MkFormDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkFormDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ title, form }, { title, form },
{ {
done: (result) => { done: (result) => {
@ -576,7 +576,7 @@ export function form(title, form) {
export async function selectUser() { export async function selectUser() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent(() => import("@/components/MkUserSelectDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkUserSelectDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{}, {},
{ {
ok: (user) => { ok: (user) => {
@ -591,9 +591,7 @@ export async function selectUser() {
export async function selectInstance(): Promise<Misskey.entities.Instance> { export async function selectInstance(): Promise<Misskey.entities.Instance> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent( defineAsyncComponent({ loader: () => import("@/components/MkInstanceSelectDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
() => import("@/components/MkInstanceSelectDialog.vue"),
),
{}, {},
{ {
ok: (instance) => { ok: (instance) => {
@ -608,9 +606,7 @@ export async function selectInstance(): Promise<Misskey.entities.Instance> {
export async function selectDriveFile(multiple: boolean) { export async function selectDriveFile(multiple: boolean) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent( defineAsyncComponent({ loader: () => import("@/components/MkDriveSelectDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
() => import("@/components/MkDriveSelectDialog.vue"),
),
{ {
type: "file", type: "file",
multiple, multiple,
@ -630,9 +626,7 @@ export async function selectDriveFile(multiple: boolean) {
export async function selectDriveFolder(multiple: boolean) { export async function selectDriveFolder(multiple: boolean) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent( defineAsyncComponent({ loader: () => import("@/components/MkDriveSelectDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
() => import("@/components/MkDriveSelectDialog.vue"),
),
{ {
type: "folder", type: "folder",
multiple, multiple,
@ -652,9 +646,7 @@ export async function selectDriveFolder(multiple: boolean) {
export async function pickEmoji(src: HTMLElement | null, opts) { export async function pickEmoji(src: HTMLElement | null, opts) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent( defineAsyncComponent({ loader: () => import("@/components/MkEmojiPickerDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
() => import("@/components/MkEmojiPickerDialog.vue"),
),
{ {
src, src,
...opts, ...opts,
@ -677,7 +669,7 @@ export async function cropImage(
): Promise<Misskey.entities.DriveFile> { ): Promise<Misskey.entities.DriveFile> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
popup( popup(
defineAsyncComponent(() => import("@/components/MkCropperDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkCropperDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
file: image, file: image,
aspectRatio: options.aspectRatio, aspectRatio: options.aspectRatio,
@ -741,7 +733,7 @@ export async function openEmojiPicker(
}); });
openingEmojiPicker = await popup( openingEmojiPicker = await popup(
defineAsyncComponent(() => import("@/components/MkEmojiPickerDialog.vue")), defineAsyncComponent({ loader: () => import("@/components/MkEmojiPickerDialog.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
src, src,
...opts, ...opts,
@ -774,7 +766,7 @@ export function popupMenu(
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let dispose; let dispose;
popup( popup(
defineAsyncComponent(() => import("@/components/MkPopupMenu.vue")), defineAsyncComponent({ loader: () => import("@/components/MkPopupMenu.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
items, items,
src, src,
@ -802,7 +794,7 @@ export function contextMenu(
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let dispose; let dispose;
popup( popup(
defineAsyncComponent(() => import("@/components/MkContextMenu.vue")), defineAsyncComponent({ loader: () => import("@/components/MkContextMenu.vue"), loadingComponent: MkWaitingDialog, delay: 1000 }),
{ {
items, items,
ev, ev,