From 1df7abfbb906f2e364b5eb5fefc5f9de5dd60026 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 18 Oct 2020 10:11:34 +0900
Subject: [PATCH] Improve waiting dialog

---
 src/client/components/page/page.post.vue      | 12 ++---
 .../{icon-dialog.vue => waiting-dialog.vue}   | 50 +++++++++++++-----
 src/client/os.ts                              | 42 ++++++++++-----
 src/client/pages/follow.vue                   | 52 ++++---------------
 src/client/pages/instance/emojis.vue          | 19 ++-----
 src/client/pages/settings/import-export.vue   | 20 +------
 src/client/pages/test.vue                     | 15 ++++++
 src/client/scripts/search.ts                  | 31 ++++-------
 8 files changed, 110 insertions(+), 131 deletions(-)
 rename src/client/components/{icon-dialog.vue => waiting-dialog.vue} (53%)

diff --git a/src/client/components/page/page.post.vue b/src/client/components/page/page.post.vue
index e2b712667a..ac8be4a397 100644
--- a/src/client/components/page/page.post.vue
+++ b/src/client/components/page/page.post.vue
@@ -44,14 +44,7 @@ export default defineComponent({
 	},
 	methods: {
 		upload() {
-			return new Promise((ok) => {
-				const dialog = os.dialog({
-					type: 'waiting',
-					text: this.$t('uploading') + '...',
-					showOkButton: false,
-					showCancelButton: false,
-					cancelableByBgClick: false
-				});
+			const promise = new Promise((ok) => {
 				const canvas = this.hpml.canvases[this.value.canvasId];
 				canvas.toBlob(blob => {
 					const data = new FormData();
@@ -67,11 +60,12 @@ export default defineComponent({
 					})
 					.then(response => response.json())
 					.then(f => {
-						dialog.close();
 						ok(f);
 					})
 				});
 			});
+			os.promiseDialog(promise);
+			return promise;
 		},
 		async post() {
 			this.posting = true;
diff --git a/src/client/components/icon-dialog.vue b/src/client/components/waiting-dialog.vue
similarity index 53%
rename from src/client/components/icon-dialog.vue
rename to src/client/components/waiting-dialog.vue
index e8eae3342f..7e8ebeaec0 100644
--- a/src/client/components/icon-dialog.vue
+++ b/src/client/components/waiting-dialog.vue
@@ -1,8 +1,9 @@
 <template>
-<MkModal ref="modal" @click="type === 'success' ? done() : () => {}" @closed="$emit('closed')">
-	<div class="iuyakobc" :class="type">
-		<Fa class="icon" v-if="type === 'success'" :icon="faCheck"/>
-		<Fa class="icon" v-else-if="type === 'waiting'" :icon="faSpinner" pulse/>
+<MkModal ref="modal" @click="success ? done() : () => {}" @closed="$emit('closed')">
+	<div class="iuyakobc" :class="{ iconOnly: (text == null) || success }">
+		<Fa class="icon success" v-if="success" :icon="faCheck"/>
+		<Fa class="icon waiting" v-else :icon="faSpinner" pulse/>
+		<div class="text" v-if="text && !success">{{ text }}<MkEllipsis/></div>
 	</div>
 </MkModal>
 </template>
@@ -18,12 +19,18 @@ export default defineComponent({
 	},
 
 	props: {
-		type: {
-			required: true
+		success: {
+			type: Boolean,
+			required: true,
 		},
 		showing: {
-			required: true
-		}
+			type: Boolean,
+			required: true,
+		},
+		text: {
+			type: String,
+			required: false,
+		},
 	},
 
 	emits: ['done', 'closed'],
@@ -57,17 +64,32 @@ export default defineComponent({
 	text-align: center;
 	background: var(--panel);
 	border-radius: var(--radius);
-	width: initial;
-	font-size: 32px;
+	width: 250px;
 
-	&.success {
-		color: var(--accent);
+	&.iconOnly {
+		padding: 0;
+		width: 96px;
+		height: 96px;
+
+		> .icon {
+			height: 100%;
+		}
 	}
 
-	&.waiting {
-		> .icon {
+	> .icon {
+		font-size: 32px;
+
+		&.success {
+			color: var(--accent);
+		}
+
+		&.waiting {
 			opacity: 0.7;
 		}
 	}
+
+	> .text {
+		margin-top: 16px;
+	}
 }
 </style>
diff --git a/src/client/os.ts b/src/client/os.ts
index 03bae65539..688bc136df 100644
--- a/src/client/os.ts
+++ b/src/client/os.ts
@@ -62,17 +62,34 @@ export function api(endpoint: string, data: Record<string, any> = {}, token?: st
 	return promise;
 }
 
-export function apiWithDialog(endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined, onSuccess?: (res: any) => void, onFailure?: (e: Error) => void) {
-	const showing = ref(true);
-	const state = ref('waiting');
-
+export function apiWithDialog(
+	endpoint: string,
+	data: Record<string, any> = {},
+	token?: string | null | undefined,
+	onSuccess?: (res: any) => void,
+	onFailure?: (e: Error) => void,
+) {
 	const promise = api(endpoint, data, token);
+	promiseDialog(promise, onSuccess, onFailure);
+
+	return promise;
+}
+
+export function promiseDialog<T extends Promise<any>>(
+	promise: T,
+	onSuccess?: (res: any) => void,
+	onFailure?: (e: Error) => void,
+	text?: string,
+): T {
+	const showing = ref(true);
+	const success = ref(false);
+
 	promise.then(res => {
 		if (onSuccess) {
 			showing.value = false;
 			onSuccess(res);
 		} else {
-			state.value = 'success';
+			success.value = true;
 			setTimeout(() => {
 				showing.value = false;
 			}, 1000);
@@ -89,9 +106,10 @@ export function apiWithDialog(endpoint: string, data: Record<string, any> = {},
 		}
 	});
 
-	popup(defineAsyncComponent(() => import('@/components/icon-dialog.vue')), {
-		type: state,
-		showing: showing
+	popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
+		success: success,
+		showing: showing,
+		text: text,
 	}, {}, 'closed');
 
 	return promise;
@@ -161,8 +179,8 @@ export function success() {
 		setTimeout(() => {
 			showing.value = false;
 		}, 1000);
-		popup(defineAsyncComponent(() => import('@/components/icon-dialog.vue')), {
-			type: 'success',
+		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
+			success: true,
 			showing: showing
 		}, {
 			done: () => resolve(),
@@ -173,8 +191,8 @@ export function success() {
 export function waiting() {
 	return new Promise((resolve, reject) => {
 		const showing = ref(true);
-		popup(defineAsyncComponent(() => import('@/components/icon-dialog.vue')), {
-			type: 'waiting',
+		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
+			success: false,
 			showing: showing
 		}, {
 			done: () => resolve(),
diff --git a/src/client/pages/follow.vue b/src/client/pages/follow.vue
index 13e0a62a0d..b1ab7f9f6b 100644
--- a/src/client/pages/follow.vue
+++ b/src/client/pages/follow.vue
@@ -6,26 +6,20 @@
 <script lang="ts">
 import { defineComponent } from 'vue';
 import * as os from '@/os';
+import parseAcct from '../../misc/acct/parse';
 
 export default defineComponent({
 	created() {
 		const acct = new URL(location.href).searchParams.get('acct');
 		if (acct == null) return;
 
-		/*
-		const dialog = os.dialog({
-			type: 'waiting',
-			text: this.$t('fetchingAsApObject') + '...',
-			showOkButton: false,
-			showCancelButton: false,
-			cancelableByBgClick: false
-		});
-		*/
+		let promise;
 
 		if (acct.startsWith('https://')) {
-			os.api('ap/show', {
+			promise = os.api('ap/show', {
 				uri: acct
-			}).then(res => {
+			});
+			promise.then(res => {
 				if (res.type == 'User') {
 					this.follow(res.object);
 				} else if (res.type === 'Note') {
@@ -38,30 +32,15 @@ export default defineComponent({
 						window.close();
 					});
 				}
-			}).catch(e => {
-				os.dialog({
-					type: 'error',
-					text: e
-				}).then(() => {
-					window.close();
-				});
-			}).finally(() => {
-				//dialog.close();
 			});
 		} else {
-			os.api('users/show', parseAcct(acct)).then(user => {
+			promise = os.api('users/show', parseAcct(acct));
+			promise.then(user => {
 				this.follow(user);
-			}).catch(e => {
-				os.dialog({
-					type: 'error',
-					text: e
-				}).then(() => {
-					window.close();
-				});
-			}).finally(() => {
-				//dialog.close();
 			});
 		}
+
+		os.promiseDialog(promise, null, null, this.$t('fetchingAsApObject'));
 	},
 
 	methods: {
@@ -77,19 +56,8 @@ export default defineComponent({
 				return;
 			}
 			
-			os.api('following/create', {
+			os.apiWithDialog('following/create', {
 				userId: user.id
-			}).then(() => {
-				os.success().then(() => {
-					window.close();
-				});
-			}).catch(e => {
-				os.dialog({
-					type: 'error',
-					text: e
-				}).then(() => {
-					window.close();
-				});
 			});
 		}
 	}
diff --git a/src/client/pages/instance/emojis.vue b/src/client/pages/instance/emojis.vue
index 465a9ebe00..b254b65765 100644
--- a/src/client/pages/instance/emojis.vue
+++ b/src/client/pages/instance/emojis.vue
@@ -106,24 +106,13 @@ export default defineComponent({
 		async add(e) {
 			const files = await selectFile(e.currentTarget || e.target, null, true);
 
-			const dialog = os.dialog({
-				type: 'waiting',
-				text: this.$t('doing') + '...',
-				showOkButton: false,
-				showCancelButton: false,
-				cancelableByBgClick: false
-			});
-			
-			Promise.all(files.map(file => os.api('admin/emoji/add', {
+			const promise = Promise.all(files.map(file => os.api('admin/emoji/add', {
 				fileId: file.id,
-			})))
-			.then(() => {
+			})));
+			promise.then(() => {
 				this.$refs.emojis.reload();
-				os.success();
-			})
-			.finally(() => {
-				dialog.cancel();
 			});
+			os.promiseDialog(promise);
 		},
 
 		async edit(emoji) {
diff --git a/src/client/pages/settings/import-export.vue b/src/client/pages/settings/import-export.vue
index a5a0085277..8943695dfd 100644
--- a/src/client/pages/settings/import-export.vue
+++ b/src/client/pages/settings/import-export.vue
@@ -69,31 +69,15 @@ export default defineComponent({
 			data.append('file', file);
 			data.append('i', this.$store.state.i.token);
 
-			const dialog = os.dialog({
-				type: 'waiting',
-				text: this.$t('uploading') + '...',
-				showOkButton: false,
-				showCancelButton: false,
-				cancelableByBgClick: false
-			});
-
-			fetch(apiUrl + '/drive/files/create', {
+			const promise = fetch(apiUrl + '/drive/files/create', {
 				method: 'POST',
 				body: data
 			})
 			.then(response => response.json())
 			.then(f => {
 				this.reqImport(f);
-			})
-			.catch(e => {
-				os.dialog({
-					type: 'error',
-					text: e
-				});
-			})
-			.finally(() => {
-				dialog.close();
 			});
+			os.promiseDialog(promise);
 		},
 
 		reqImport(file) {
diff --git a/src/client/pages/test.vue b/src/client/pages/test.vue
index 02b4d1614d..b053b859bb 100644
--- a/src/client/pages/test.vue
+++ b/src/client/pages/test.vue
@@ -106,6 +106,14 @@
 			</div>
 		</div>
 
+		<div class="_card _vMargin">
+			<div class="_title">Waiting dialog</div>
+			<div class="_content">
+				<MkButton inline @click="openWaitingDialog()">icon only</MkButton>
+				<MkButton inline @click="openWaitingDialog('Doing')">with text</MkButton>
+			</div>
+		</div>
+
 		<div class="_card _vMargin">
 			<div class="_title">Messaging window</div>
 			<div class="_content">
@@ -224,6 +232,13 @@ export default defineComponent({
 			os.pageWindow('/my/messaging', defineAsyncComponent(() => import('@/pages/messaging/index.vue')));
 		},
 
+		openWaitingDialog(text?) {
+			const promise = new Promise((resolve, reject) => {
+				setTimeout(resolve, 2000);
+			});
+			os.promiseDialog(promise, null, null, text);
+		},
+
 		resetTutorial() {
 			this.$store.dispatch('settings/set', { key: 'tutorial', value: 0 });
 		},
diff --git a/src/client/scripts/search.ts b/src/client/scripts/search.ts
index fbdc32dfb1..540aba2a92 100644
--- a/src/client/scripts/search.ts
+++ b/src/client/scripts/search.ts
@@ -48,29 +48,18 @@ export async function search(q?: string | null | undefined) {
 	}
 
 	if (q.startsWith('https://')) {
-		/*
-		const dialog = os.dialog({
-			type: 'waiting',
-			text: i18n.global.t('fetchingAsApObject') + '...',
-			showOkButton: false,
-			showCancelButton: false,
-			cancelableByBgClick: false
+		const promise = os.api('ap/show', {
+			uri: q
 		});
-		*/
 
-		try {
-			const res = await os.api('ap/show', {
-				uri: q
-			});
-			//dialog.cancel();
-			if (res.type === 'User') {
-				router.push(`/@${res.object.username}@${res.object.host}`);
-			} else if (res.type === 'Note') {
-				router.push(`/notes/${res.object.id}`);
-			}
-		} catch (e) {
-			//dialog.cancel();
-			// TODO: Show error
+		os.promiseDialog(promise, null, null, i18n.global.t('fetchingAsApObject'));
+
+		const res = await promise;
+
+		if (res.type === 'User') {
+			router.push(`/@${res.object.username}@${res.object.host}`);
+		} else if (res.type === 'Note') {
+			router.push(`/notes/${res.object.id}`);
 		}
 
 		return;