Refactoring

This commit is contained in:
syuilo 2020-03-31 09:11:43 +09:00
parent 10356b4041
commit 28f8933c3c
3 changed files with 101 additions and 95 deletions

View file

@ -20,6 +20,7 @@ import Menu from './components/menu.vue';
import { router } from './router'; import { router } from './router';
import { applyTheme, lightTheme, builtinThemes } from './theme'; import { applyTheme, lightTheme, builtinThemes } from './theme';
import { isDeviceDarkmode } from './scripts/is-device-darkmode'; import { isDeviceDarkmode } from './scripts/is-device-darkmode';
import createStore from './store';
Vue.use(Vuex); Vue.use(Vuex);
Vue.use(VueHotkey); Vue.use(VueHotkey);
@ -134,36 +135,38 @@ document.body.setAttribute('ontouchstart', '');
// アプリ基底要素マウント // アプリ基底要素マウント
document.body.innerHTML = '<div id="app"></div>'; document.body.innerHTML = '<div id="app"></div>';
const os = new MiOS(); const store = createStore();
const os = new MiOS(store);
os.init(async () => { os.init(async () => {
window.addEventListener('storage', e => { window.addEventListener('storage', e => {
if (e.key === 'vuex') { if (e.key === 'vuex') {
os.store.replaceState(JSON.parse(localStorage['vuex'])); store.replaceState(JSON.parse(localStorage['vuex']));
} else if (e.key === 'i') { } else if (e.key === 'i') {
location.reload(); location.reload();
} }
}, false) }, false)
os.store.watch(state => state.device.darkMode, darkMode => { store.watch(state => state.device.darkMode, darkMode => {
// TODO: このファイルでbuiltinThemesを参照するとcode splittingが効かず、初回読み込み時に全てのテーマコードを読み込むことになってしまい無駄なので何とかする // TODO: このファイルでbuiltinThemesを参照するとcode splittingが効かず、初回読み込み時に全てのテーマコードを読み込むことになってしまい無駄なので何とかする
const themes = builtinThemes.concat(os.store.state.device.themes); const themes = builtinThemes.concat(store.state.device.themes);
applyTheme(themes.find(x => x.id === (darkMode ? os.store.state.device.darkTheme : os.store.state.device.lightTheme))); applyTheme(themes.find(x => x.id === (darkMode ? store.state.device.darkTheme : store.state.device.lightTheme)));
}); });
//#region Sync dark mode //#region Sync dark mode
if (os.store.state.device.syncDeviceDarkMode) { if (store.state.device.syncDeviceDarkMode) {
os.store.commit('device/set', { key: 'darkMode', value: isDeviceDarkmode() }); store.commit('device/set', { key: 'darkMode', value: isDeviceDarkmode() });
} }
window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => { window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => {
if (os.store.state.device.syncDeviceDarkMode) { if (store.state.device.syncDeviceDarkMode) {
os.store.commit('device/set', { key: 'darkMode', value: mql.matches }); store.commit('device/set', { key: 'darkMode', value: mql.matches });
} }
}); });
//#endregion //#endregion
if ('Notification' in window && os.store.getters.isSignedIn) { if ('Notification' in window && store.getters.isSignedIn) {
// 許可を得ていなかったらリクエスト // 許可を得ていなかったらリクエスト
if (Notification.permission === 'default') { if (Notification.permission === 'default') {
Notification.requestPermission(); Notification.requestPermission();
@ -171,7 +174,7 @@ os.init(async () => {
} }
const app = new Vue({ const app = new Vue({
store: os.store, store: store,
metaInfo: { metaInfo: {
title: null, title: null,
titleTemplate: title => title ? `${title} | ${(instanceName || 'Misskey')}` : (instanceName || 'Misskey') titleTemplate: title => title ? `${title} | ${(instanceName || 'Misskey')}` : (instanceName || 'Misskey')
@ -183,7 +186,7 @@ os.init(async () => {
}; };
}, },
methods: { methods: {
api: os.api, api: (endpoint: string, data: { [x: string]: any } = {}, token?) => store.dispatch('api', { endpoint, data, token }),
signout: os.signout, signout: os.signout,
new(vm, props) { new(vm, props) {
const x = new vm({ const x = new vm({
@ -234,58 +237,58 @@ os.init(async () => {
// マウント // マウント
app.$mount('#app'); app.$mount('#app');
if (app.$store.getters.isSignedIn) { if (store.getters.isSignedIn) {
const main = os.stream.useSharedConnection('main'); const main = os.stream.useSharedConnection('main');
// 自分の情報が更新されたとき // 自分の情報が更新されたとき
main.on('meUpdated', i => { main.on('meUpdated', i => {
app.$store.dispatch('mergeMe', i); store.dispatch('mergeMe', i);
}); });
main.on('readAllNotifications', () => { main.on('readAllNotifications', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadNotification: false hasUnreadNotification: false
}); });
}); });
main.on('unreadNotification', () => { main.on('unreadNotification', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadNotification: true hasUnreadNotification: true
}); });
}); });
main.on('unreadMention', () => { main.on('unreadMention', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadMentions: true hasUnreadMentions: true
}); });
}); });
main.on('readAllUnreadMentions', () => { main.on('readAllUnreadMentions', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadMentions: false hasUnreadMentions: false
}); });
}); });
main.on('unreadSpecifiedNote', () => { main.on('unreadSpecifiedNote', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: true hasUnreadSpecifiedNotes: true
}); });
}); });
main.on('readAllUnreadSpecifiedNotes', () => { main.on('readAllUnreadSpecifiedNotes', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: false hasUnreadSpecifiedNotes: false
}); });
}); });
main.on('readAllMessagingMessages', () => { main.on('readAllMessagingMessages', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadMessagingMessage: false hasUnreadMessagingMessage: false
}); });
}); });
main.on('unreadMessagingMessage', () => { main.on('unreadMessagingMessage', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadMessagingMessage: true hasUnreadMessagingMessage: true
}); });
@ -293,13 +296,13 @@ os.init(async () => {
}); });
main.on('readAllAntennas', () => { main.on('readAllAntennas', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadAntenna: false hasUnreadAntenna: false
}); });
}); });
main.on('unreadAntenna', () => { main.on('unreadAntenna', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadAntenna: true hasUnreadAntenna: true
}); });
@ -307,13 +310,13 @@ os.init(async () => {
}); });
main.on('readAllAnnouncements', () => { main.on('readAllAnnouncements', () => {
app.$store.dispatch('mergeMe', { store.dispatch('mergeMe', {
hasUnreadAnnouncement: false hasUnreadAnnouncement: false
}); });
}); });
main.on('clientSettingUpdated', x => { main.on('clientSettingUpdated', x => {
app.$store.commit('settings/set', { store.commit('settings/set', {
key: x.key, key: x.key,
value: x.value value: x.value
}); });

View file

@ -2,16 +2,11 @@ import autobind from 'autobind-decorator';
import Vue from 'vue'; import Vue from 'vue';
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import initStore from './store';
import { apiUrl, version } from './config'; import { apiUrl, version } from './config';
import Progress from './scripts/loading'; import Progress from './scripts/loading';
import Stream from './scripts/stream'; import Stream from './scripts/stream';
import store from './store';
//#region api requests
let spinner = null;
let pending = 0;
//#endregion
/** /**
* Misskey Operating System * Misskey Operating System
@ -19,7 +14,7 @@ let pending = 0;
export default class MiOS extends EventEmitter { export default class MiOS extends EventEmitter {
public app: Vue; public app: Vue;
public store: ReturnType<typeof initStore>; public store: ReturnType<typeof store>;
/** /**
* A connection manager of home stream * A connection manager of home stream
@ -31,6 +26,11 @@ export default class MiOS extends EventEmitter {
*/ */
private swRegistration: ServiceWorkerRegistration = null; private swRegistration: ServiceWorkerRegistration = null;
constructor(vuex: MiOS['store']) {
super();
this.store = vuex;
}
@autobind @autobind
public signout() { public signout() {
this.store.dispatch('logout'); this.store.dispatch('logout');
@ -52,8 +52,6 @@ export default class MiOS extends EventEmitter {
}); });
}; };
this.store = initStore(this);
// ユーザーをフェッチしてコールバックする // ユーザーをフェッチしてコールバックする
const fetchme = (token, cb) => { const fetchme = (token, cb) => {
let me = null; let me = null;
@ -187,10 +185,13 @@ export default class MiOS extends EventEmitter {
} }
// Register // Register
this.api('sw/register', { this.store.dispatch('api', {
endpoint: subscription.endpoint, endpoint: 'sw/register',
auth: encode(subscription.getKey('auth')), data: {
publickey: encode(subscription.getKey('p256dh')) endpoint: subscription.endpoint,
auth: encode(subscription.getKey('auth')),
publickey: encode(subscription.getKey('p256dh'))
}
}); });
}) })
// When subscribe failed // When subscribe failed
@ -214,52 +215,6 @@ export default class MiOS extends EventEmitter {
// Register service worker // Register service worker
navigator.serviceWorker.register(sw); navigator.serviceWorker.register(sw);
} }
/**
* Misskey APIにリクエストします
* @param endpoint
* @param data
*/
@autobind
public api(endpoint: string, data: { [x: string]: any } = {}, token?): Promise<{ [x: string]: any }> {
if (++pending === 1) {
spinner = document.createElement('div');
spinner.setAttribute('id', 'wait');
document.body.appendChild(spinner);
}
const onFinally = () => {
if (--pending === 0) spinner.parentNode.removeChild(spinner);
};
const promise = new Promise((resolve, reject) => {
// Append a credential
if (this.store.getters.isSignedIn) (data as any).i = this.store.state.i.token;
if (token) (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;
}
} }
/** /**

View file

@ -1,8 +1,7 @@
import Vuex from 'vuex'; import Vuex 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 { apiUrl } from './config';
import MiOS from './mios';
const defaultSettings = { const defaultSettings = {
tutorial: 0, tutorial: 0,
@ -57,13 +56,15 @@ function copy<T>(data: T): T {
return JSON.parse(JSON.stringify(data)); return JSON.parse(JSON.stringify(data));
} }
export default (os: MiOS) => new Vuex.Store({ export default () => new Vuex.Store({
plugins: [createPersistedState({ plugins: [createPersistedState({
paths: ['i', 'device', 'deviceUser', 'settings', 'instance'] paths: ['i', 'device', 'deviceUser', 'settings', 'instance']
})], })],
state: { state: {
i: null, i: null,
pendingApiRequestsCount: 0,
spinner: null
}, },
getters: { getters: {
@ -121,6 +122,47 @@ export default (os: MiOS) => new Vuex.Store({
ctx.commit('settings/init', me.clientData); ctx.commit('settings/init', me.clientData);
} }
}, },
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) (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: {
@ -139,9 +181,12 @@ export default (os: MiOS) => new Vuex.Store({
actions: { actions: {
async fetch(ctx) { async fetch(ctx) {
const meta = await os.api('meta', { const meta = await ctx.dispatch('api', {
detail: false endpoint: 'meta',
}); data: {
detail: false
}
}, { root: true });
ctx.commit('set', meta); ctx.commit('set', meta);
} }
@ -246,10 +291,13 @@ export default (os: MiOS) => new Vuex.Store({
ctx.commit('set', x); ctx.commit('set', x);
if (ctx.rootGetters.isSignedIn) { if (ctx.rootGetters.isSignedIn) {
os.api('i/update-client-setting', { ctx.dispatch('api', {
name: x.key, endpoint: 'i/update-client-setting',
value: x.value data: {
}); name: x.key,
value: x.value
}
}, { root: true });
} }
}, },
} }