This commit is contained in:
syuilo 2020-09-06 14:25:42 +09:00
parent 73a94798c4
commit 26b16124e6
5 changed files with 72 additions and 82 deletions

View File

@ -1,5 +1,5 @@
<template> <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 }"> <div class="mk-dialog" :class="{ iconOnly }">
<template v-if="type == 'signin'"> <template v-if="type == 'signin'">
<mk-signin/> <mk-signin/>
@ -160,7 +160,7 @@ export default defineComponent({
methods: { methods: {
done(canceled, result?) { done(canceled, result?) {
this.showing = false; this.showing = false;
this.$store.commit('dialogDone', { id: this.id, result: { canceled, result } }); os.dialogCallbacks[this.id]({ canceled, result });
}, },
async ok() { async ok() {

View File

@ -1,5 +1,5 @@
<template> <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' }"> <div class="rrevdjwt" :class="{ left: align === 'left' }" ref="items" :style="{ width: width + 'px' }">
<template v-for="(item, i) in items.filter(item => item !== undefined)"> <template v-for="(item, i) in items.filter(item => item !== undefined)">
<div v-if="item === null" class="divider" :key="i"></div> <div v-if="item === null" class="divider" :key="i"></div>
@ -103,7 +103,6 @@ export default defineComponent({
}, },
close() { close() {
this.showing = false; this.showing = false;
this.$store.commit('dialogDone', { id: this.id });
}, },
focusUp() { focusUp() {
focusPrev(document.activeElement); focusPrev(document.activeElement);

View File

@ -1,22 +1,57 @@
import { defineAsyncComponent } from 'vue'; import { defineAsyncComponent } from 'vue';
import Stream from '@/scripts/stream'; import Stream from '@/scripts/stream';
import { store } from './store'; import { store } from '@/store';
import { apiUrl } from '@/config';
export const stream = new Stream(); export const stream = new Stream();
export const dialogCallbacks = {};
export function api(endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) { 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>) { export function dialog(props: Record<string, any>) {
return store.dispatch('showDialog', { return store.dispatch('popup', {
component: defineAsyncComponent(() => import('@/components/dialog.vue')), component: defineAsyncComponent(() => import('@/components/dialog.vue')),
props props
}); });
} }
export function menu(props: Record<string, any>) { export function menu(props: Record<string, any>) {
return store.dispatch('showDialog', { return store.dispatch('popup', {
component: defineAsyncComponent(() => import('@/components/menu.vue')), component: defineAsyncComponent(() => import('@/components/menu.vue')),
props props
}); });

View File

@ -2,7 +2,9 @@
<DeckUI v-if="deckmode"/> <DeckUI v-if="deckmode"/>
<DefaultUI v-else/> <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> </template>
<script lang="ts"> <script lang="ts">

View File

@ -1,11 +1,11 @@
import { reactive, watch } from 'vue';
import { createStore } from 'vuex'; import { createStore } from 'vuex';
import createPersistedState from 'vuex-persistedstate'; import createPersistedState from 'vuex-persistedstate';
import * as nestedProperty from 'nested-property'; 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 { 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 { faBell, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons';
import { AiScript, utils, values } from '@syuilo/aiscript'; 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'; import { erase } from '../prelude/array';
export const defaultSettings = { export const defaultSettings = {
@ -108,11 +108,10 @@ export const store = createStore({
i: null, i: null,
pendingApiRequestsCount: 0, pendingApiRequestsCount: 0,
spinner: null, spinner: null,
dialogs: [] as { popups: [] as {
id: any; id: any;
component: any; component: any;
props: Record<string, any>; props: Record<string, any>;
result: any;
}[], }[],
fullView: false, fullView: false,
@ -262,17 +261,20 @@ export const store = createStore({
state.i[key] = value; state.i[key] = value;
}, },
addDialog(state, dialog) { beginApiRequest(state) {
state.dialogs.push(dialog); state.pendingApiRequestsCount++;
}, },
dialogDone(state, { id: dialogId, result }) { endApiRequest(state) {
const dialog = state.dialogs.find(d => d.id === dialogId); state.pendingApiRequestsCount--;
dialog.result = result;
}, },
removeDialog(state, dialogId) { addPopup(state, popup) {
state.dialogs = state.dialogs.filter(d => d.id !== dialogId); state.popups.push(popup);
},
removePopup(state, popupId) {
state.popups = state.popups.filter(x => x.id !== popupId);
}, },
setFullView(state, v) { setFullView(state, v) {
@ -370,66 +372,24 @@ export const store = createStore({
} }
}, },
showDialog(ctx, { component, props }) { popup(ctx, { component, props }) {
return new Promise((res, rej) => { return new Promise((res, rej) => {
const id = Math.random().toString(); // TODO: uuidとか使う const id = Math.random().toString(); // TODO: uuidとか使う
const dialog = reactive({ const popup = {
component, component,
props: { props: {
...props, ...props,
id id
}, },
result: null,
id, id,
}); };
ctx.commit('addDialog', dialog); dialogCallbacks[id] = result => {
const unwatch = watch(() => dialog.result, result => { delete dialogCallbacks[id];
unwatch();
res(result); 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: { modules: {
@ -448,12 +408,9 @@ export const store = createStore({
actions: { actions: {
async fetch(ctx) { async fetch(ctx) {
const meta = await ctx.dispatch('api', { const meta = await api('meta', {
endpoint: 'meta', detail: false
data: { });
detail: false
}
}, { root: true });
ctx.commit('set', meta); ctx.commit('set', meta);
} }
@ -716,13 +673,10 @@ export const store = createStore({
ctx.commit('set', x); ctx.commit('set', x);
if (ctx.rootGetters.isSignedIn) { if (ctx.rootGetters.isSignedIn) {
ctx.dispatch('api', { api('i/update-client-setting', {
endpoint: 'i/update-client-setting', name: x.key,
data: { value: x.value
name: x.key, });
value: x.value
}
}, { root: true });
} }
}, },
} }