From 26b16124e633c2af71a575425a06aafdfa754608 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sun, 6 Sep 2020 14:25:42 +0900
Subject: [PATCH] wip

---
 src/client/components/dialog.vue |   4 +-
 src/client/components/menu.vue   |   3 +-
 src/client/os.ts                 |  43 +++++++++++--
 src/client/root.vue              |   4 +-
 src/client/store.ts              | 100 +++++++++----------------------
 5 files changed, 72 insertions(+), 82 deletions(-)

diff --git a/src/client/components/dialog.vue b/src/client/components/dialog.vue
index 6d4b5b115a..52d9dea8b1 100644
--- a/src/client/components/dialog.vue
+++ b/src/client/components/dialog.vue
@@ -1,5 +1,5 @@
 <template>
-<x-modal ref="modal" @closed="$store.commit('removeDialog', id)" @click="onBgClick" :showing="showing">
+<x-modal ref="modal" @closed="$store.commit('removePopup', id)" @click="onBgClick" :showing="showing">
 	<div class="mk-dialog" :class="{ iconOnly }">
 		<template v-if="type == 'signin'">
 			<mk-signin/>
@@ -160,7 +160,7 @@ export default defineComponent({
 	methods: {
 		done(canceled, result?) {
 			this.showing = false;
-			this.$store.commit('dialogDone', { id: this.id, result: { canceled, result } });
+			os.dialogCallbacks[this.id]({ canceled, result });
 		},
 
 		async ok() {
diff --git a/src/client/components/menu.vue b/src/client/components/menu.vue
index 425c28e295..71920b15e8 100644
--- a/src/client/components/menu.vue
+++ b/src/client/components/menu.vue
@@ -1,5 +1,5 @@
 <template>
-<x-modal :source="source" :no-center="noCenter" ref="popup" @click="close()" @closed="$store.commit('removeDialog', id)" :showing="showing">
+<x-modal :source="source" :no-center="noCenter" ref="popup" @click="close()" @closed="$store.commit('removePopup', id)" :showing="showing">
 	<div class="rrevdjwt" :class="{ left: align === 'left' }" ref="items" :style="{ width: width + 'px' }">
 		<template v-for="(item, i) in items.filter(item => item !== undefined)">
 			<div v-if="item === null" class="divider" :key="i"></div>
@@ -103,7 +103,6 @@ export default defineComponent({
 		},
 		close() {
 			this.showing = false;
-			this.$store.commit('dialogDone', { id: this.id });
 		},
 		focusUp() {
 			focusPrev(document.activeElement);
diff --git a/src/client/os.ts b/src/client/os.ts
index f3d3c72bdd..c58204a071 100644
--- a/src/client/os.ts
+++ b/src/client/os.ts
@@ -1,22 +1,57 @@
 import { defineAsyncComponent } from 'vue';
 import Stream from '@/scripts/stream';
-import { store } from './store';
+import { store } from '@/store';
+import { apiUrl } from '@/config';
 
 export const stream = new Stream();
 
+export const dialogCallbacks = {};
+
 export function api(endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) {
-	return store.dispatch('api', { endpoint, data, token });
+	store.commit('beginApiRequest');
+
+	const onFinally = () => {
+		store.commit('endApiRequest');
+	};
+
+	const promise = new Promise((resolve, reject) => {
+		// Append a credential
+		if (store.getters.isSignedIn) (data as any).i = store.state.i.token;
+		if (token !== undefined) (data as any).i = token;
+
+		// Send request
+		fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
+			method: 'POST',
+			body: JSON.stringify(data),
+			credentials: 'omit',
+			cache: 'no-cache'
+		}).then(async (res) => {
+			const body = res.status === 204 ? null : await res.json();
+
+			if (res.status === 200) {
+				resolve(body);
+			} else if (res.status === 204) {
+				resolve();
+			} else {
+				reject(body.error);
+			}
+		}).catch(reject);
+	});
+
+	promise.then(onFinally, onFinally);
+
+	return promise;
 }
 
 export function dialog(props: Record<string, any>) {
-	return store.dispatch('showDialog', {
+	return store.dispatch('popup', {
 		component: defineAsyncComponent(() => import('@/components/dialog.vue')),
 		props
 	});
 }
 
 export function menu(props: Record<string, any>) {
-	return store.dispatch('showDialog', {
+	return store.dispatch('popup', {
 		component: defineAsyncComponent(() => import('@/components/menu.vue')),
 		props
 	});
diff --git a/src/client/root.vue b/src/client/root.vue
index 3ca565d191..b2232fa45c 100644
--- a/src/client/root.vue
+++ b/src/client/root.vue
@@ -2,7 +2,9 @@
 <DeckUI v-if="deckmode"/>
 <DefaultUI v-else/>
 
-<component v-for="dialog in $store.state.dialogs" :is="dialog.component" v-bind="dialog.props" :key="dialog.id"/>
+<component v-for="popup in $store.state.popups" :is="popup.component" v-bind="popup.props" :key="popup.id"/>
+
+<div id="wait" v-if="$store.state.pendingApiRequestsCount > 0"></div>
 </template>
 
 <script lang="ts">
diff --git a/src/client/store.ts b/src/client/store.ts
index 8dd682a8e9..f9ce4eed9e 100644
--- a/src/client/store.ts
+++ b/src/client/store.ts
@@ -1,11 +1,11 @@
-import { reactive, watch } from 'vue';
 import { createStore } from 'vuex';
 import createPersistedState from 'vuex-persistedstate';
 import * as nestedProperty from 'nested-property';
 import { faSatelliteDish, faTerminal, faHashtag, faBroadcastTower, faFireAlt, faSearch, faStar, faAt, faListUl, faUserClock, faUsers, faCloud, faGamepad, faFileAlt, faSatellite, faDoorClosed, faColumns } from '@fortawesome/free-solid-svg-icons';
 import { faBell, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons';
 import { AiScript, utils, values } from '@syuilo/aiscript';
-import { apiUrl, deckmode } from '@/config';
+import { deckmode } from '@/config';
+import { api, dialogCallbacks } from '@/os';
 import { erase } from '../prelude/array';
 
 export const defaultSettings = {
@@ -108,11 +108,10 @@ export const store = createStore({
 		i: null,
 		pendingApiRequestsCount: 0,
 		spinner: null,
-		dialogs: [] as {
+		popups: [] as {
 			id: any;
 			component: any;
 			props: Record<string, any>;
-			result: any;
 		}[],
 		fullView: false,
 
@@ -262,17 +261,20 @@ export const store = createStore({
 			state.i[key] = value;
 		},
 
-		addDialog(state, dialog) {
-			state.dialogs.push(dialog);
+		beginApiRequest(state) {
+			state.pendingApiRequestsCount++;
 		},
 
-		dialogDone(state, { id: dialogId, result }) {
-			const dialog = state.dialogs.find(d => d.id === dialogId);
-			dialog.result = result;
+		endApiRequest(state) {
+			state.pendingApiRequestsCount--;
 		},
 
-		removeDialog(state, dialogId) {
-			state.dialogs = state.dialogs.filter(d => d.id !== dialogId);
+		addPopup(state, popup) {
+			state.popups.push(popup);
+		},
+
+		removePopup(state, popupId) {
+			state.popups = state.popups.filter(x => x.id !== popupId);
 		},
 
 		setFullView(state, v) {
@@ -370,66 +372,24 @@ export const store = createStore({
 			}
 		},
 
-		showDialog(ctx, { component, props }) {
+		popup(ctx, { component, props }) {
 			return new Promise((res, rej) => {
 				const id = Math.random().toString(); // TODO: uuidとか使う
-				const dialog = reactive({
+				const popup = {
 					component,
 					props: {
 						...props,
 						id
 					},
-					result: null,
 					id,
-				});
-				ctx.commit('addDialog', dialog);
-				const unwatch = watch(() => dialog.result, result => {
-					unwatch();
+				};
+				dialogCallbacks[id] = result => {
+					delete dialogCallbacks[id];
 					res(result);
-				});
+				};
+				ctx.commit('addPopup', popup);
 			});
 		},
-
-		api(ctx, { endpoint, data, token }) {
-			if (++ctx.state.pendingApiRequestsCount === 1) {
-				// TODO: spinnerの表示はstoreでやらない
-				ctx.state.spinner = document.createElement('div');
-				ctx.state.spinner.setAttribute('id', 'wait');
-				document.body.appendChild(ctx.state.spinner);
-			}
-
-			const onFinally = () => {
-				if (--ctx.state.pendingApiRequestsCount === 0) ctx.state.spinner.parentNode.removeChild(ctx.state.spinner);
-			};
-
-			const promise = new Promise((resolve, reject) => {
-				// Append a credential
-				if (ctx.getters.isSignedIn) (data as any).i = ctx.state.i.token;
-				if (token !== undefined) (data as any).i = token;
-
-				// Send request
-				fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
-					method: 'POST',
-					body: JSON.stringify(data),
-					credentials: 'omit',
-					cache: 'no-cache'
-				}).then(async (res) => {
-					const body = res.status === 204 ? null : await res.json();
-
-					if (res.status === 200) {
-						resolve(body);
-					} else if (res.status === 204) {
-						resolve();
-					} else {
-						reject(body.error);
-					}
-				}).catch(reject);
-			});
-
-			promise.then(onFinally, onFinally);
-
-			return promise;
-		}
 	},
 
 	modules: {
@@ -448,12 +408,9 @@ export const store = createStore({
 
 			actions: {
 				async fetch(ctx) {
-					const meta = await ctx.dispatch('api', {
-						endpoint: 'meta',
-						data: {
-							detail: false
-						}
-					}, { root: true });
+					const meta = await api('meta', {
+						detail: false
+					});
 
 					ctx.commit('set', meta);
 				}
@@ -716,13 +673,10 @@ export const store = createStore({
 					ctx.commit('set', x);
 
 					if (ctx.rootGetters.isSignedIn) {
-						ctx.dispatch('api', {
-							endpoint: 'i/update-client-setting',
-							data: {
-								name: x.key,
-								value: x.value
-							}
-						}, { root: true });
+						api('i/update-client-setting', {
+							name: x.key,
+							value: x.value
+						});
 					}
 				},
 			}