This commit is contained in:
syuilo 2018-02-21 15:30:03 +09:00
parent 1eecc1fa3d
commit de6d77d0cb
29 changed files with 422 additions and 426 deletions

View file

@ -2,7 +2,7 @@ import Vue from 'vue';
export default function<T extends object>(data: {
name: string;
props?: T;
props?: () => T;
}) {
return Vue.extend({
props: {
@ -17,20 +17,9 @@ export default function<T extends object>(data: {
},
data() {
return {
props: data.props || {} as T
props: data.props ? data.props() : {} as T
};
},
watch: {
props(newProps, oldProps) {
if (JSON.stringify(newProps) == JSON.stringify(oldProps)) return;
(this as any).api('i/update_home', {
id: this.id,
data: newProps
}).then(() => {
(this as any).os.i.client_settings.home.find(w => w.id == this.id).data = newProps;
});
}
},
created() {
if (this.props) {
Object.keys(this.props).forEach(prop => {
@ -39,6 +28,18 @@ export default function<T extends object>(data: {
}
});
}
this.$watch('props', newProps => {
console.log(this.id, newProps);
(this as any).api('i/update_home', {
id: this.id,
data: newProps
}).then(() => {
(this as any).os.i.client_settings.home.find(w => w.id == this.id).data = newProps;
});
}, {
deep: true
});
}
});
}

View file

@ -1,9 +1,8 @@
import { EventEmitter } from 'eventemitter3';
import * as riot from 'riot';
import api from './scripts/api';
import signout from './scripts/signout';
import Progress from './scripts/loading';
import HomeStreamManager from './scripts/streaming/home-stream-manager';
import api from './scripts/api';
import DriveStreamManager from './scripts/streaming/drive-stream-manager';
import ServerStreamManager from './scripts/streaming/server-stream-manager';
import RequestsStreamManager from './scripts/streaming/requests-stream-manager';
@ -226,22 +225,8 @@ export default class MiOS extends EventEmitter {
// フェッチが完了したとき
const fetched = me => {
if (me) {
riot.observable(me);
// この me オブジェクトを更新するメソッド
me.update = data => {
if (data) Object.assign(me, data);
me.trigger('updated');
};
// ローカルストレージにキャッシュ
localStorage.setItem('me', JSON.stringify(me));
// 自分の情報が更新されたとき
me.on('updated', () => {
// キャッシュ更新
localStorage.setItem('me', JSON.stringify(me));
});
}
this.i = me;
@ -270,8 +255,6 @@ export default class MiOS extends EventEmitter {
// 後から新鮮なデータをフェッチ
fetchme(cachedMe.token, freshData => {
Object.assign(cachedMe, freshData);
cachedMe.trigger('updated');
cachedMe.trigger('refreshed');
});
} else {
// Get token from cookie

View file

@ -16,7 +16,9 @@ export default class Connection extends Stream {
}, 1000 * 60);
// 自分の情報が更新されたとき
this.on('i_updated', me.update);
this.on('i_updated', i => {
Object.assign(me, i);
});
// トークンが再生成されたとき
// このままではAPIが利用できないので強制的にサインアウトさせる

View file

@ -1,318 +0,0 @@
<mk-channel-home-widget>
<template v-if="!data.compact">
<p class="title">%fa:tv%{
channel ? channel.title : '%i18n:desktop.tags.mk-channel-home-widget.title%'
}</p>
<button @click="settings" title="%i18n:desktop.tags.mk-channel-home-widget.settings%">%fa:cog%</button>
</template>
<p class="get-started" v-if="this.data.channel == null">%i18n:desktop.tags.mk-channel-home-widget.get-started%</p>
<mk-channel ref="channel" show={ this.data.channel }/>
<style lang="stylus" scoped>
:scope
display block
background #fff
border solid 1px rgba(0, 0, 0, 0.075)
border-radius 6px
overflow hidden
> .title
z-index 2
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color #888
box-shadow 0 1px rgba(0, 0, 0, 0.07)
> [data-fa]
margin-right 4px
> button
position absolute
z-index 2
top 0
right 0
padding 0
width 42px
font-size 0.9em
line-height 42px
color #ccc
&:hover
color #aaa
&:active
color #999
> .get-started
margin 0
padding 16px
text-align center
color #aaa
> mk-channel
height 200px
</style>
<script lang="typescript">
this.data = {
channel: null,
compact: false
};
this.mixin('widget');
this.on('mount', () => {
if (this.data.channel) {
this.zap();
}
});
this.zap = () => {
this.update({
fetching: true
});
this.$root.$data.os.api('channels/show', {
channel_id: this.data.channel
}).then(channel => {
this.update({
fetching: false,
channel: channel
});
this.$refs.channel.zap(channel);
});
};
this.settings = () => {
const id = window.prompt('チャンネルID');
if (!id) return;
this.data.channel = id;
this.zap();
// Save state
this.save();
};
this.func = () => {
this.data.compact = !this.data.compact;
this.save();
};
</script>
</mk-channel-home-widget>
<mk-channel>
<p v-if="fetching">読み込み中<mk-ellipsis/></p>
<div v-if="!fetching" ref="posts">
<p v-if="posts.length == 0">まだ投稿がありません</p>
<mk-channel-post each={ post in posts.slice().reverse() } post={ post } form={ parent.refs.form }/>
</div>
<mk-channel-form ref="form"/>
<style lang="stylus" scoped>
:scope
display block
> p
margin 0
padding 16px
text-align center
color #aaa
> div
height calc(100% - 38px)
overflow auto
font-size 0.9em
> mk-channel-post
border-bottom solid 1px #eee
&:last-child
border-bottom none
> mk-channel-form
position absolute
left 0
bottom 0
</style>
<script lang="typescript">
import ChannelStream from '../../../common/scripts/streaming/channel-stream';
this.mixin('api');
this.fetching = true;
this.channel = null;
this.posts = [];
this.on('unmount', () => {
if (this.connection) {
this.connection.off('post', this.onPost);
this.connection.close();
}
});
this.zap = channel => {
this.update({
fetching: true,
channel: channel
});
this.$root.$data.os.api('channels/posts', {
channel_id: channel.id
}).then(posts => {
this.update({
fetching: false,
posts: posts
});
this.scrollToBottom();
if (this.connection) {
this.connection.off('post', this.onPost);
this.connection.close();
}
this.connection = new ChannelStream(this.channel.id);
this.connection.on('post', this.onPost);
});
};
this.onPost = post => {
this.posts.unshift(post);
this.update();
this.scrollToBottom();
};
this.scrollToBottom = () => {
this.$refs.posts.scrollTop = this.$refs.posts.scrollHeight;
};
</script>
</mk-channel>
<mk-channel-post>
<header>
<a class="index" @click="reply">{ post.index }:</a>
<a class="name" href={ _URL_ + '/' + post.user.username }><b>{ post.user.name }</b></a>
<span>ID:<i>{ post.user.username }</i></span>
</header>
<div>
<a v-if="post.reply">&gt;&gt;{ post.reply.index }</a>
{ post.text }
<div class="media" v-if="post.media">
<template each={ file in post.media }>
<a href={ file.url } target="_blank">
<img src={ file.url + '?thumbnail&size=512' } alt={ file.name } title={ file.name }/>
</a>
</template>
</div>
</div>
<style lang="stylus" scoped>
:scope
display block
margin 0
padding 0
color #444
> header
position -webkit-sticky
position sticky
z-index 1
top 0
padding 8px 4px 4px 16px
background rgba(255, 255, 255, 0.9)
> .index
margin-right 0.25em
> .name
margin-right 0.5em
color #008000
> div
padding 0 16px 16px 16px
> .media
> a
display inline-block
> img
max-width 100%
vertical-align bottom
</style>
<script lang="typescript">
this.post = this.opts.post;
this.form = this.opts.form;
this.reply = () => {
this.form.refs.text.value = `>>${ this.post.index } `;
};
</script>
</mk-channel-post>
<mk-channel-form>
<input ref="text" disabled={ wait } onkeydown={ onkeydown } placeholder="書いて">
<style lang="stylus" scoped>
:scope
display block
width 100%
height 38px
padding 4px
border-top solid 1px #ddd
> input
padding 0 8px
width 100%
height 100%
font-size 14px
color #55595c
border solid 1px #dadada
border-radius 4px
&:hover
&:focus
border-color #aeaeae
</style>
<script lang="typescript">
this.mixin('api');
this.clear = () => {
this.$refs.text.value = '';
};
this.onkeydown = e => {
if (e.which == 10 || e.which == 13) this.post();
};
this.post = () => {
this.update({
wait: true
});
let text = this.$refs.text.value;
let reply = null;
if (/^>>([0-9]+) /.test(text)) {
const index = text.match(/^>>([0-9]+) /)[1];
reply = this.parent.posts.find(p => p.index.toString() == index);
text = text.replace(/^>>([0-9]+) /, '');
}
this.$root.$data.os.api('posts/create', {
text: text,
reply_id: reply ? reply.id : undefined,
channel_id: this.parent.channel.id
}).then(data => {
this.clear();
}).catch(err => {
alert('失敗した');
}).then(() => {
this.update({
wait: false
});
});
};
</script>
</mk-channel-form>

View file

@ -23,6 +23,7 @@ import MkIndex from './views/pages/index.vue';
import MkUser from './views/pages/user/user.vue';
import MkSelectDrive from './views/pages/selectdrive.vue';
import MkDrive from './views/pages/drive.vue';
import MkHomeCustomize from './views/pages/home-customize.vue';
/**
* init
@ -66,6 +67,8 @@ init(async (launch) => {
app.$router.addRoutes([{
path: '/', name: 'index', component: MkIndex
}, {
path: '/i/customize-home', component: MkHomeCustomize
}, {
path: '/i/drive', component: MkDrive
}, {

View file

@ -1,5 +1,5 @@
<template>
<div class="mk-calendar">
<div class="mk-calendar" :data-melt="design == 4 || design == 5">
<template v-if="design == 0 || design == 1">
<button @click="prev" title="%i18n:desktop.tags.mk-calendar-widget.prev%">%fa:chevron-circle-left%</button>
<p class="title">{{ '%i18n:desktop.tags.mk-calendar-widget.title%'.replace('{1}', year).replace('{2}', month) }}</p>

View file

@ -40,7 +40,7 @@
<div v-for="place in ['left', 'main', 'right']" :class="place" :ref="place" :data-place="place">
<template v-if="place != 'main'">
<template v-for="widget in widgets[place]">
<div class="customize-container" v-if="customize" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)">
<div class="customize-container" v-if="customize" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)" :data-widget-id="widget.id">
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id"/>
</div>
<template v-else>
@ -60,7 +60,7 @@
<script lang="ts">
import Vue from 'vue';
import * as uuid from 'uuid';
import Sortable from 'sortablejs';
import * as Sortable from 'sortablejs';
export default Vue.extend({
props: {
@ -72,7 +72,6 @@ export default Vue.extend({
},
data() {
return {
home: [],
bakedHomeData: null,
widgetAdderSelected: null
};
@ -95,16 +94,15 @@ export default Vue.extend({
},
rightEl(): Element {
return (this.$refs.right as Element[])[0];
},
home(): any {
return (this as any).os.i.client_settings.home;
}
},
created() {
this.bakedHomeData = this.bakeHomeData();
},
mounted() {
(this as any).os.i.on('refreshed', this.onMeRefreshed);
this.home = (this as any).os.i.client_settings.home;
this.$nextTick(() => {
if (!this.customize) {
if (this.leftEl.children.length == 0) {
@ -132,7 +130,7 @@ export default Vue.extend({
animation: 150,
onMove: evt => {
const id = evt.dragged.getAttribute('data-widget-id');
this.home.find(tag => tag.id == id).widget.place = evt.to.getAttribute('data-place');
this.home.find(w => w.id == id).place = evt.to.getAttribute('data-place');
},
onSort: () => {
this.saveHome();
@ -153,24 +151,15 @@ export default Vue.extend({
}
});
},
beforeDestroy() {
(this as any).os.i.off('refreshed', this.onMeRefreshed);
},
methods: {
bakeHomeData() {
return JSON.stringify((this as any).os.i.client_settings.home);
return JSON.stringify(this.home);
},
onTlLoaded() {
this.$emit('loaded');
},
onMeRefreshed() {
if (this.bakedHomeData != this.bakeHomeData()) {
// TODO: i18n
alert('別の場所でホームが編集されました。ページを再度読み込みすると編集が反映されます。');
}
},
onWidgetContextmenu(widgetId) {
(this.$refs[widgetId] as any).func();
(this.$refs[widgetId] as any)[0].func();
},
addWidget() {
const widget = {
@ -180,29 +169,13 @@ export default Vue.extend({
data: {}
};
(this as any).os.i.client_settings.home.unshift(widget);
this.home.unshift(widget);
this.saveHome();
},
saveHome() {
const data = [];
Array.from(this.leftEl.children).forEach(el => {
const id = el.getAttribute('data-widget-id');
const widget = (this as any).os.i.client_settings.home.find(w => w.id == id);
widget.place = 'left';
data.push(widget);
});
Array.from(this.rightEl.children).forEach(el => {
const id = el.getAttribute('data-widget-id');
const widget = (this as any).os.i.client_settings.home.find(w => w.id == id);
widget.place = 'right';
data.push(widget);
});
(this as any).api('i/update_home', {
home: data
home: this.home
});
},
warp(date) {

View file

@ -35,6 +35,7 @@ import wDonation from './widgets/donation.vue';
import wNotifications from './widgets/notifications.vue';
import wBroadcast from './widgets/broadcast.vue';
import wTimemachine from './widgets/timemachine.vue';
import wProfile from './widgets/profile.vue';
Vue.component('mk-ui', ui);
Vue.component('mk-ui-notification', uiNotification);
@ -71,3 +72,4 @@ Vue.component('mkw-donation', wDonation);
Vue.component('mkw-notifications', wNotifications);
Vue.component('mkw-broadcast', wBroadcast);
Vue.component('mkw-timemachine', wTimemachine);
Vue.component('mkw-profile', wProfile);

View file

@ -10,10 +10,10 @@
import define from '../../../../common/define-widget';
export default define({
name: 'activity',
props: {
props: () => ({
design: 0,
view: 0
}
})
}).extend({
methods: {
func() {

View file

@ -25,9 +25,9 @@ import { lang } from '../../../../config';
export default define({
name: 'broadcast',
props: {
props: () => ({
design: 0
}
})
}).extend({
data() {
return {

View file

@ -38,9 +38,9 @@
import define from '../../../../common/define-widget';
export default define({
name: 'calendar',
props: {
props: () => ({
design: 0
}
})
}).extend({
data() {
return {

View file

@ -0,0 +1,67 @@
<template>
<div class="form">
<input v-model="text" :disabled="wait" @keydown="onKeydown" placeholder="書いて">
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
data() {
return {
text: '',
wait: false
};
},
methods: {
onKeydown(e) {
if (e.which == 10 || e.which == 13) this.post();
},
post() {
this.wait = true;
let reply = null;
if (/^>>([0-9]+) /.test(this.text)) {
const index = this.text.match(/^>>([0-9]+) /)[1];
reply = (this.$parent as any).posts.find(p => p.index.toString() == index);
this.text = this.text.replace(/^>>([0-9]+) /, '');
}
(this as any).api('posts/create', {
text: this.text,
reply_id: reply ? reply.id : undefined,
channel_id: (this.$parent as any).channel.id
}).then(data => {
this.text = '';
}).catch(err => {
alert('失敗した');
}).then(() => {
this.wait = false;
});
}
}
});
</script>
<style lang="stylus" scoped>
.form
width 100%
height 38px
padding 4px
border-top solid 1px #ddd
> input
padding 0 8px
width 100%
height 100%
font-size 14px
color #55595c
border solid 1px #dadada
border-radius 4px
&:hover
&:focus
border-color #aeaeae
</style>

View file

@ -0,0 +1,64 @@
<template>
<div class="post">
<header>
<a class="index" @click="reply">{{ post.index }}:</a>
<router-link class="name" :to="`/${post.user.username}`" v-user-preview="post.user.id"><b>{{ post.user.name }}</b></router-link>
<span>ID:<i>{{ post.user.username }}</i></span>
</header>
<div>
<a v-if="post.reply">&gt;&gt;{{ post.reply.index }}</a>
{{ post.text }}
<div class="media" v-if="post.media">
<a v-for="file in post.media" :href="file.url" target="_blank">
<img :src="`${file.url}?thumbnail&size=512`" :alt="file.name" :title="file.name"/>
</a>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: ['post'],
methods: {
reply() {
this.$emit('reply', this.post);
}
}
});
</script>
<style lang="stylus" scoped>
.post
margin 0
padding 0
color #444
> header
position -webkit-sticky
position sticky
z-index 1
top 0
padding 8px 4px 4px 16px
background rgba(255, 255, 255, 0.9)
> .index
margin-right 0.25em
> .name
margin-right 0.5em
color #008000
> div
padding 0 16px 16px 16px
> .media
> a
display inline-block
> img
max-width 100%
vertical-align bottom
</style>

View file

@ -0,0 +1,104 @@
<template>
<div class="channel">
<p v-if="fetching">読み込み中<mk-ellipsis/></p>
<div v-if="!fetching" ref="posts">
<p v-if="posts.length == 0">まだ投稿がありません</p>
<x-post class="post" v-for="post in posts.slice().reverse()" :post="post" :key="post.id" @reply="reply"/>
</div>
<x-form class="form" ref="form"/>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import ChannelStream from '../../../../common/scripts/streaming/channel-stream';
import XForm from './channel.channel.form.vue';
import XPost from './channel.channel.post.vue';
export default Vue.extend({
components: {
XForm,
XPost
},
props: ['channel'],
data() {
return {
fetching: true,
posts: [],
connection: null
};
},
watch: {
channel() {
this.zap();
}
},
mounted() {
this.zap();
},
beforeDestroy() {
this.disconnect();
},
methods: {
zap() {
this.fetching = true;
(this as any).api('channels/posts', {
channel_id: this.channel.id
}).then(posts => {
this.posts = posts;
this.fetching = false;
this.scrollToBottom();
this.disconnect();
this.connection = new ChannelStream(this.channel.id);
this.connection.on('post', this.onPost);
});
},
disconnect() {
if (this.connection) {
this.connection.off('post', this.onPost);
this.connection.close();
}
},
onPost(post) {
this.posts.unshift(post);
this.scrollToBottom();
},
scrollToBottom() {
(this.$refs.posts as any).scrollTop = (this.$refs.posts as any).scrollHeight;
},
reply(post) {
(this.$refs.form as any).text = `>>${ post.index } `;
}
}
});
</script>
<style lang="stylus" scoped>
.channel
> p
margin 0
padding 16px
text-align center
color #aaa
> div
height calc(100% - 38px)
overflow auto
font-size 0.9em
> .post
border-bottom solid 1px #eee
&:last-child
border-bottom none
> .form
position absolute
left 0
bottom 0
</style>

View file

@ -0,0 +1,107 @@
<template>
<div class="mkw-channel">
<template v-if="!data.compact">
<p class="title">%fa:tv%{{ channel ? channel.title : '%i18n:desktop.tags.mk-channel-home-widget.title%' }}</p>
<button @click="settings" title="%i18n:desktop.tags.mk-channel-home-widget.settings%">%fa:cog%</button>
</template>
<p class="get-started" v-if="props.channel == null">%i18n:desktop.tags.mk-channel-home-widget.get-started%</p>
<x-channel class="channel" :channel="channel" v-else/>
</div>
</template>
<script lang="ts">
import define from '../../../../common/define-widget';
import XChannel from './channel.channel.vue';
export default define({
name: 'server',
props: () => ({
channel: null,
compact: false
})
}).extend({
components: {
XChannel
},
data() {
return {
fetching: true,
channel: null
};
},
mounted() {
if (this.props.channel) {
this.zap();
}
},
methods: {
func() {
this.props.compact = !this.props.compact;
},
settings() {
const id = window.prompt('チャンネルID');
if (!id) return;
this.props.channel = id;
this.zap();
},
zap() {
this.fetching = true;
(this as any).api('channels/show', {
channel_id: this.props.channel
}).then(channel => {
this.channel = channel;
this.fetching = false;
});
}
}
});
</script>
<style lang="stylus" scoped>
.mkw-channel
background #fff
border solid 1px rgba(0, 0, 0, 0.075)
border-radius 6px
overflow hidden
> .title
z-index 2
margin 0
padding 0 16px
line-height 42px
font-size 0.9em
font-weight bold
color #888
box-shadow 0 1px rgba(0, 0, 0, 0.07)
> [data-fa]
margin-right 4px
> button
position absolute
z-index 2
top 0
right 0
padding 0
width 42px
font-size 0.9em
line-height 42px
color #ccc
&:hover
color #aaa
&:active
color #999
> .get-started
margin 0
padding 16px
text-align center
color #aaa
> .channel
height 200px
</style>

View file

@ -9,9 +9,9 @@
import define from '../../../../common/define-widget';
export default define({
name: 'messaging',
props: {
props: () => ({
design: 0
}
})
}).extend({
methods: {
navigate(user) {

View file

@ -12,9 +12,9 @@
import define from '../../../../common/define-widget';
export default define({
name: 'notifications',
props: {
props: () => ({
compact: false
}
})
}).extend({
methods: {
settings() {

View file

@ -13,9 +13,9 @@
import define from '../../../../common/define-widget';
export default define({
name: 'photo-stream',
props: {
props: () => ({
design: 0
}
})
}).extend({
data() {
return {

View file

@ -18,9 +18,9 @@
import define from '../../../../common/define-widget';
export default define({
name: 'polls',
props: {
props: () => ({
compact: false
}
})
}).extend({
data() {
return {

View file

@ -12,9 +12,9 @@
import define from '../../../../common/define-widget';
export default define({
name: 'post-form',
props: {
props: () => ({
design: 0
}
})
}).extend({
data() {
return {

View file

@ -4,19 +4,19 @@
:data-melt="props.design == 2"
>
<div class="banner"
style={ I.banner_url ? 'background-image: url(' + I.banner_url + '?thumbnail&size=256)' : '' }
:style="os.i.banner_url ? `background-image: url(${os.i.banner_url}?thumbnail&size=256)` : ''"
title="クリックでバナー編集"
@click="wapi_setBanner"
@click="os.apis.updateBanner"
></div>
<img class="avatar"
src={ I.avatar_url + '?thumbnail&size=96' }
@click="wapi_setAvatar"
:src="`${os.i.avatar_url}?thumbnail&size=96`"
@click="os.apis.updateAvatar"
alt="avatar"
title="クリックでアバター編集"
v-user-preview={ I.id }
v-user-preview="os.i.id"
/>
<a class="name" href={ '/' + I.username }>{ I.name }</a>
<p class="username">@{ I.username }</p>
<router-link class="name" :to="`/${os.i.username}`">{{ os.i.name }}</router-link>
<p class="username">@{{ os.i.username }}</p>
</div>
</template>
@ -24,9 +24,9 @@
import define from '../../../../common/define-widget';
export default define({
name: 'profile',
props: {
props: () => ({
design: 0
}
})
}).extend({
methods: {
func() {

View file

@ -15,9 +15,9 @@
import define from '../../../../common/define-widget';
export default define({
name: 'rss',
props: {
props: () => ({
compact: false
}
})
}).extend({
data() {
return {

View file

@ -27,10 +27,10 @@ import XInfo from './server.info.vue';
export default define({
name: 'server',
props: {
props: () => ({
design: 0,
view: 0
}
})
}).extend({
components: {
XCpuMemory,

View file

@ -15,10 +15,10 @@ import * as anime from 'animejs';
import define from '../../../../common/define-widget';
export default define({
name: 'slideshow',
props: {
props: () => ({
folder: undefined,
size: 0
}
})
}).extend({
data() {
return {

View file

@ -8,9 +8,9 @@
import define from '../../../../common/define-widget';
export default define({
name: 'timemachine',
props: {
props: () => ({
design: 0
}
})
}).extend({
methods: {
chosen(date) {

View file

@ -17,9 +17,9 @@
import define from '../../../../common/define-widget';
export default define({
name: 'trends',
props: {
props: () => ({
compact: false
}
})
}).extend({
data() {
return {

View file

@ -28,9 +28,9 @@ const limit = 3;
export default define({
name: 'users',
props: {
props: () => ({
compact: false
}
})
}).extend({
data() {
return {

View file

@ -103,6 +103,14 @@ export default (callback: (launch: (api: (os: MiOS) => API) => [Vue, MiOS]) => v
router: new VueRouter({
mode: 'history'
}),
created() {
this.$watch('os.i', i => {
// キャッシュ更新
localStorage.setItem('me', JSON.stringify(i));
}, {
deep: true
});
},
render: createEl => createEl(App)
}).$mount('#app');