summaryrefslogtreecommitdiff
path: root/src/client/app
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2018-08-03 21:32:59 +0900
committerGitHub <noreply@github.com>2018-08-03 21:32:59 +0900
commit6da9da0e8fafa7489b93ee6e2f91bafe96f742c4 (patch)
tree396fa98a0d76cdbc76b73ed69ac03956fc0d9610 /src/client/app
parent#332 (#2065) (diff)
parentwip (diff)
downloadsharkey-6da9da0e8fafa7489b93ee6e2f91bafe96f742c4.tar.gz
sharkey-6da9da0e8fafa7489b93ee6e2f91bafe96f742c4.tar.bz2
sharkey-6da9da0e8fafa7489b93ee6e2f91bafe96f742c4.zip
Merge pull request #2077 from syuilo/#2066
なんか
Diffstat (limited to 'src/client/app')
-rw-r--r--src/client/app/common/views/components/games/reversi/reversi.game.vue29
-rw-r--r--src/client/app/common/views/components/games/reversi/reversi.index.vue258
-rw-r--r--src/client/app/common/views/components/games/reversi/reversi.room.vue251
-rw-r--r--src/client/app/common/views/components/games/reversi/reversi.vue221
-rw-r--r--src/client/app/common/views/components/index.ts4
-rw-r--r--src/client/app/common/views/components/ui/form/button.vue86
-rw-r--r--src/client/app/common/views/components/ui/form/radio.vue126
7 files changed, 660 insertions, 315 deletions
diff --git a/src/client/app/common/views/components/games/reversi/reversi.game.vue b/src/client/app/common/views/components/games/reversi/reversi.game.vue
index 34e9705dd4..bbfec2c1cc 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.game.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.game.vue
@@ -1,14 +1,14 @@
<template>
-<div class="root">
+<div class="xqnhankfuuilcwvhgsopeqncafzsquya">
<header><b>{{ blackUser | userName }}</b>(%i18n:common.reversi.black%) vs <b>{{ whiteUser | userName }}</b>(%i18n:common.reversi.white%)</header>
<div style="overflow: hidden">
- <p class="turn" v-if="!iAmPlayer && !game.isEnded">{{ '%i18n:common.reversi.turn-of%'.replace('{}', Vue.filter('userName')(turnUser)) }}<mk-ellipsis/></p>
- <p class="turn" v-if="logPos != logs.length">{{ '%i18n:common.reversi.past-turn-of%'.replace('{}', Vue.filter('userName')(turnUser)) }}</p>
+ <p class="turn" v-if="!iAmPlayer && !game.isEnded">{{ '%i18n:common.reversi.turn-of%'.replace('{}', $options.filters.userName(turnUser)) }}<mk-ellipsis/></p>
+ <p class="turn" v-if="logPos != logs.length">{{ '%i18n:common.reversi.past-turn-of%'.replace('{}', $options.filters.userName(turnUser)) }}</p>
<p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn">%i18n:common.reversi.opponent-turn%<mk-ellipsis/></p>
<p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn" v-animate-css="{ classes: 'tada', iteration: 'infinite' }">%i18n:common.reversi.my-turn%</p>
<p class="result" v-if="game.isEnded && logPos == logs.length">
- <template v-if="game.winner">{{ '%i18n:common.reversi.won%'.replace('{}', Vue.filter('userName')(game.winner)) }}{{ game.settings.isLlotheo ? ' (ロセオ)' : '' }}</template>
+ <template v-if="game.winner">{{ '%i18n:common.reversi.won%'.replace('{}', $options.filters.userName(game.winner)) }}{{ game.settings.isLlotheo ? ' (ロセオ)' : '' }}</template>
<template v-else>%i18n:common.reversi.drawn%</template>
</p>
</div>
@@ -258,12 +258,12 @@ export default Vue.extend({
<style lang="stylus" scoped>
@import '~const.styl'
-.root
+root(isDark)
text-align center
> header
padding 8px
- border-bottom dashed 1px #c4cdd4
+ border-bottom dashed 1px isDark ? #4c5761 : #c4cdd4
> .board
width calc(100% - 16px)
@@ -327,16 +327,16 @@ export default Vue.extend({
user-select none
&.empty
- border solid 2px #eee
+ border solid 2px isDark ? #51595f : #eee
&.empty.can
- background #eee
+ background isDark ? #51595f : #eee
&.empty.myTurn
- border-color #ddd
+ border-color isDark ? #6a767f : #ddd
&.can
- background #eee
+ background isDark ? #51595f : #eee
cursor pointer
&:hover
@@ -350,7 +350,7 @@ export default Vue.extend({
box-shadow 0 0 0 4px rgba($theme-color, 0.7)
&.isEnded
- border-color #ddd
+ border-color isDark ? #6a767f : #ddd
&.none
border-color transparent !important
@@ -388,4 +388,11 @@ export default Vue.extend({
display inline-block
margin 0 8px
min-width 70px
+
+.xqnhankfuuilcwvhgsopeqncafzsquya[data-darkmode]
+ root(true)
+
+.xqnhankfuuilcwvhgsopeqncafzsquya:not([data-darkmode])
+ root(false)
+
</style>
diff --git a/src/client/app/common/views/components/games/reversi/reversi.index.vue b/src/client/app/common/views/components/games/reversi/reversi.index.vue
new file mode 100644
index 0000000000..026159a0fd
--- /dev/null
+++ b/src/client/app/common/views/components/games/reversi/reversi.index.vue
@@ -0,0 +1,258 @@
+<template>
+<div class="phgnkghfpyvkrvwiajkiuoxyrdaqpzcx">
+ <h1>%i18n:@title%</h1>
+ <p>%i18n:@sub-title%</p>
+ <div class="play">
+ <!--<el-button round>フリーマッチ(準備中)</el-button>-->
+ <form-button primary round @click="match">%i18n:@invite%</form-button>
+ <details>
+ <summary>%i18n:@rule%</summary>
+ <div>
+ <p>%i18n:@rule-desc%</p>
+ <dl>
+ <dt><b>%i18n:@mode-invite%</b></dt>
+ <dd>%i18n:@mode-invite-desc%</dd>
+ </dl>
+ </div>
+ </details>
+ </div>
+ <section v-if="invitations.length > 0">
+ <h2>%i18n:@invitations%</h2>
+ <div class="invitation" v-for="i in invitations" tabindex="-1" @click="accept(i)">
+ <mk-avatar class="avatar" :user="i.parent"/>
+ <span class="name"><b>{{ i.parent | userName }}</b></span>
+ <span class="username">@{{ i.parent.username }}</span>
+ <mk-time :time="i.createdAt"/>
+ </div>
+ </section>
+ <section v-if="myGames.length > 0">
+ <h2>%i18n:@my-games%</h2>
+ <a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
+ <mk-avatar class="avatar" :user="g.user1"/>
+ <mk-avatar class="avatar" :user="g.user2"/>
+ <span><b>{{ g.user1 | userName }}</b> vs <b>{{ g.user2 | userName }}</b></span>
+ <span class="state">{{ g.isEnded ? '%i18n:@game-state.ended%' : '%i18n:@game-state.playing%' }}</span>
+ </a>
+ </section>
+ <section v-if="games.length > 0">
+ <h2>%i18n:@all-games%</h2>
+ <a class="game" v-for="g in games" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
+ <mk-avatar class="avatar" :user="g.user1"/>
+ <mk-avatar class="avatar" :user="g.user2"/>
+ <span><b>{{ g.user1 | userName }}</b> vs <b>{{ g.user2 | userName }}</b></span>
+ <span class="state">{{ g.isEnded ? '%i18n:@game-state.ended%' : '%i18n:@game-state.playing%' }}</span>
+ </a>
+ </section>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+
+export default Vue.extend({
+ data() {
+ return {
+ games: [],
+ gamesFetching: true,
+ gamesMoreFetching: false,
+ myGames: [],
+ matching: null,
+ invitations: [],
+ connection: null,
+ connectionId: null
+ };
+ },
+
+ mounted() {
+ if (this.$store.getters.isSignedIn) {
+ this.connection = (this as any).os.streams.reversiStream.getConnection();
+ this.connectionId = (this as any).os.streams.reversiStream.use();
+
+ this.connection.on('invited', this.onInvited);
+
+ (this as any).api('games/reversi/games', {
+ my: true
+ }).then(games => {
+ this.myGames = games;
+ });
+
+ (this as any).api('games/reversi/invitations').then(invitations => {
+ this.invitations = this.invitations.concat(invitations);
+ });
+ }
+
+ (this as any).api('games/reversi/games').then(games => {
+ this.games = games;
+ this.gamesFetching = false;
+ });
+ },
+
+ beforeDestroy() {
+ if (this.connection) {
+ this.connection.off('invited', this.onInvited);
+ (this as any).os.streams.reversiStream.dispose(this.connectionId);
+ }
+ },
+
+ methods: {
+ go(game) {
+ (this as any).api('games/reversi/games/show', {
+ gameId: game.id
+ }).then(game => {
+ this.$emit('go', game);
+ });
+ },
+
+ match() {
+ (this as any).apis.input({
+ title: '%i18n:@enter-username%'
+ }).then(username => {
+ (this as any).api('users/show', {
+ username
+ }).then(user => {
+ (this as any).api('games/reversi/match', {
+ userId: user.id
+ }).then(res => {
+ if (res == null) {
+ this.$emit('matching', user);
+ } else {
+ this.$emit('go', res);
+ }
+ });
+ });
+ });
+ },
+
+ accept(invitation) {
+ (this as any).api('games/reversi/match', {
+ userId: invitation.parent.id
+ }).then(game => {
+ if (game) {
+ this.$emit('go', game);
+ }
+ });
+ },
+
+ onInvited(invite) {
+ this.invitations.unshift(invite);
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+ > h1
+ margin 0
+ padding 24px
+ font-size 24px
+ text-align center
+ font-weight normal
+ color #fff
+ background linear-gradient(to bottom, isDark ? #45730e : #8bca3e, isDark ? #464300 : #d6cf31)
+
+ & + p
+ margin 0
+ padding 12px
+ margin-bottom 12px
+ text-align center
+ font-size 14px
+ border-bottom solid 1px isDark ? #535f65 : #d3d9dc
+
+ > .play
+ margin 0 auto
+ padding 0 16px
+ max-width 500px
+ text-align center
+
+ > details
+ margin 8px 0
+
+ > div
+ padding 16px
+ font-size 14px
+ text-align left
+ background isDark ? #282c37 : #f5f5f5
+ border-radius 8px
+
+ > section
+ margin 0 auto
+ padding 0 16px 16px 16px
+ max-width 500px
+ border-top solid 1px isDark ? #535f65 : #d3d9dc
+
+ > h2
+ margin 0
+ padding 16px 0 8px 0
+ font-size 16px
+ font-weight bold
+
+ .invitation
+ margin 8px 0
+ padding 8px
+ color isDark ? #fff : #677f84
+ background isDark ? #282c37 : #fff
+ box-shadow 0 2px 16px rgba(#000, isDark ? 0.7 : 0.15)
+ border-radius 6px
+ cursor pointer
+
+ *
+ pointer-events none
+ user-select none
+
+ &:focus
+ border-color $theme-color
+
+ &:hover
+ background isDark ? #313543 : #f5f5f5
+
+ &:active
+ background isDark ? #1e222b : #eee
+
+ > .avatar
+ width 32px
+ height 32px
+ border-radius 100%
+
+ > span
+ margin 0 8px
+ line-height 32px
+
+ .game
+ display block
+ margin 8px 0
+ padding 8px
+ color isDark ? #fff : #677f84
+ background isDark ? #282c37 : #fff
+ box-shadow 0 2px 16px rgba(#000, isDark ? 0.7 : 0.15)
+ border-radius 6px
+ cursor pointer
+
+ *
+ pointer-events none
+ user-select none
+
+ &:hover
+ background isDark ? #313543 : #f5f5f5
+
+ &:active
+ background isDark ? #1e222b : #eee
+
+ > .avatar
+ width 32px
+ height 32px
+ border-radius 100%
+
+ > span
+ margin 0 8px
+ line-height 32px
+
+.phgnkghfpyvkrvwiajkiuoxyrdaqpzcx[data-darkmode]
+ root(true)
+
+.phgnkghfpyvkrvwiajkiuoxyrdaqpzcx:not([data-darkmode])
+ root(false)
+
+</style>
diff --git a/src/client/app/common/views/components/games/reversi/reversi.room.vue b/src/client/app/common/views/components/games/reversi/reversi.room.vue
index 94b36d0870..de5040f630 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.room.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.room.vue
@@ -1,78 +1,94 @@
<template>
-<div class="root">
+<div class="urbixznjwwuukfsckrwzwsqzsxornqij">
<header><b>{{ game.user1 | userName }}</b> vs <b>{{ game.user2 | userName }}</b></header>
<div>
<p>%i18n:@settings-of-the-game%</p>
- <el-card class="map">
- <div slot="header">
- <el-select :class="$style.mapSelect" v-model="mapName" placeholder="%i18n:@choose-map%" @change="onMapChange">
- <el-option label="%i18n:@random%" :value="null"/>
- <el-option-group v-for="c in mapCategories" :key="c" :label="c">
- <el-option v-for="m in maps" v-if="m.category == c" :key="m.name" :label="m.name" :value="m.name">
- <span style="float: left">{{ m.name }}</span>
- <span style="float: right; color: #8492a6; font-size: 13px" v-if="m.author">(by <i>{{ m.author }}</i>)</span>
- </el-option>
- </el-option-group>
- </el-select>
- </div>
- <div :class="$style.board" v-if="game.settings.map != null" :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }">
- <div v-for="(x, i) in game.settings.map.join('')"
- :data-none="x == ' '"
- @click="onPixelClick(i, x)"
- >
- <template v-if="x == 'b'">%fa:circle%</template>
- <template v-if="x == 'w'">%fa:circle R%</template>
+ <div class="card map">
+ <header>
+ <select v-model="mapName" placeholder="%i18n:@choose-map%" @change="onMapChange">
+ <option label="-Custom-" :value="mapName" v-if="mapName == '-Custom-'"/>
+ <option label="%i18n:@random%" :value="null"/>
+ <optgroup v-for="c in mapCategories" :key="c" :label="c">
+ <option v-for="m in maps" v-if="m.category == c" :key="m.name" :label="m.name" :value="m.name">{{ m.name }}</option>
+ </optgroup>
+ </select>
+ </header>
+
+ <div>
+ <div class="random" v-if="game.settings.map == null">%fa:dice%</div>
+ <div class="board" v-else :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }">
+ <div v-for="(x, i) in game.settings.map.join('')"
+ :data-none="x == ' '"
+ @click="onPixelClick(i, x)">
+ <template v-if="x == 'b'"><template v-if="$store.state.device.darkmode">%fa:circle R%</template><template v-else>%fa:circle%</template></template>
+ <template v-if="x == 'w'"><template v-if="$store.state.device.darkmode">%fa:circle%</template><template v-else>%fa:circle R%</template></template>
+ </div>
</div>
</div>
- </el-card>
+ </div>
- <el-card class="bw">
- <div slot="header">
+ <div class="card">
+ <header>
<span>%i18n:@black-or-white%</span>
+ </header>
+
+ <div>
+ <form-radio v-model="game.settings.bw" value="random" @change="updateSettings">%i18n:@random%</form-radio>
+ <form-radio v-model="game.settings.bw" :value="1" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}<b>{{ game.user1 | userName }}</b>{{ '%i18n:@black-is%'.split('{}')[1] }}</form-radio>
+ <form-radio v-model="game.settings.bw" :value="2" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}<b>{{ game.user2 | userName }}</b>{{ '%i18n:@black-is%'.split('{}')[1] }}</form-radio>
</div>
- <el-radio v-model="game.settings.bw" label="random" @change="updateSettings">%i18n:@random%</el-radio>
- <el-radio v-model="game.settings.bw" :label="1" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user1 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</el-radio>
- <el-radio v-model="game.settings.bw" :label="2" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user2 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</el-radio>
- </el-card>
+ </div>
- <el-card class="rules">
- <div slot="header">
+ <div class="card">
+ <header>
<span>%i18n:@rules%</span>
+ </header>
+
+ <div>
+ <mk-switch v-model="game.settings.isLlotheo" @change="updateSettings" text="%i18n:@is-llotheo%"/>
+ <mk-switch v-model="game.settings.loopedBoard" @change="updateSettings" text="%i18n:@looped-map%"/>
+ <mk-switch v-model="game.settings.canPutEverywhere" @change="updateSettings" text="%i18n:@can-put-everywhere%"/>
</div>
- <mk-switch v-model="game.settings.isLlotheo" @change="updateSettings" text="%i18n:@is-llotheo%"/>
- <mk-switch v-model="game.settings.loopedBoard" @change="updateSettings" text="%i18n:@looped-map%"/>
- <mk-switch v-model="game.settings.canPutEverywhere" @change="updateSettings" text="%i18n:@can-put-everywhere%"/>
- </el-card>
+ </div>
- <el-card class="bot-form" v-if="form">
- <div slot="header">
+ <div class="card" v-if="form">
+ <header>
<span>%i18n:@settings-of-the-bot%</span>
- </div>
- <el-alert v-for="message in messages"
- :title="message.text"
- :type="message.type"
- :key="message.id"
- />
- <template v-for="item in form">
- <mk-switch v-if="item.type == 'button'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm($event, item)">{{ item.desc || '' }}</mk-switch>
+ </header>
- <el-card v-if="item.type == 'radio'" :key="item.id">
- <div slot="header">
- <span>{{ item.label }}</span>
+ <div>
+ <el-alert v-for="message in messages"
+ :title="message.text"
+ :type="message.type"
+ :key="message.id"/>
+
+ <template v-for="item in form">
+ <mk-switch v-if="item.type == 'button'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm($event, item)">{{ item.desc || '' }}</mk-switch>
+
+ <div class="card" v-if="item.type == 'radio'" :key="item.id">
+ <header>
+ <span>{{ item.label }}</span>
+ </header>
+
+ <div>
+ <el-radio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :label="r.value" @change="onChangeForm($event, item)">{{ r.label }}</el-radio>
+ </div>
</div>
- <el-radio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :label="r.value" @change="onChangeForm($event, item)">{{ r.label }}</el-radio>
- </el-card>
- <el-card v-if="item.type == 'textbox'" :key="item.id">
- <div slot="header">
- <span>{{ item.label }}</span>
+ <div class="card" v-if="item.type == 'textbox'" :key="item.id">
+ <header>
+ <span>{{ item.label }}</span>
+ </header>
+
+ <div>
+ <el-input v-model="item.value" @change="onChangeForm($event, item)"/>
+ </div>
</div>
- <el-input v-model="item.value" @change="onChangeForm($event, item)"/>
- </el-card>
- </template>
- </el-card>
+ </template>
+ </div>
+ </div>
</div>
<footer>
@@ -84,9 +100,9 @@
</p>
<div class="actions">
- <el-button @click="exit">%i18n:@cancel%</el-button>
- <el-button type="primary" @click="accept" v-if="!isAccepted">%i18n:@ready%</el-button>
- <el-button type="primary" @click="cancel" v-if="isAccepted">%i18n:@cancel-ready%</el-button>
+ <form-button @click="exit">%i18n:@cancel%</form-button>
+ <form-button primary @click="accept" v-if="!isAccepted">%i18n:@ready%</form-button>
+ <form-button primary @click="cancel" v-if="isAccepted">%i18n:@cancel-ready%</form-button>
</div>
</footer>
</div>
@@ -202,11 +218,11 @@ export default Vue.extend({
});
},
- onMapChange(v) {
- if (v == null) {
+ onMapChange() {
+ if (this.mapName == null) {
this.game.settings.map = null;
} else {
- this.game.settings.map = Object.values(maps).find(x => x.name == v).data;
+ this.game.settings.map = Object.values(maps).find(x => x.name == this.mapName).data;
}
this.$forceUpdate();
this.updateSettings();
@@ -233,9 +249,9 @@ export default Vue.extend({
<style lang="stylus" scoped>
@import '~const.styl'
-.root
+root(isDark)
text-align center
- background #f9f9f9
+ background isDark ? #191b22 : #f9f9f9
> header
padding 8px
@@ -244,54 +260,87 @@ export default Vue.extend({
> div
padding 0 16px
- > .map
- > .bw
- > .rules
- > .bot-form
- max-width 400px
+ > .card
margin 0 auto 16px auto
+ &.map
+ > header
+ > select
+ width 100%
+ padding 12px 14px
+ background isDark ? #282C37 : #fff
+ border 1px solid isDark ? #6a707d : #dcdfe6
+ border-radius 4px
+ color isDark ? #fff : #606266
+ cursor pointer
+ transition border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1)
+
+ &:hover
+ border-color isDark ? #a7aebd : #c0c4cc
+
+ &:focus
+ &:active
+ border-color $theme-color
+
+ > div
+ > .random
+ padding 32px 0
+ font-size 64px
+ color isDark ? #4e5961 : #d8d8d8
+
+ > .board
+ display grid
+ grid-gap 4px
+ width 300px
+ height 300px
+ margin 0 auto
+ color isDark ? #fff : #444
+
+ > div
+ background transparent
+ border solid 2px isDark ? #6a767f : #ddd
+ border-radius 6px
+ overflow hidden
+ cursor pointer
+
+ *
+ pointer-events none
+ user-select none
+ width 100%
+ height 100%
+
+ &[data-none]
+ border-color transparent
+
+ .card
+ max-width 400px
+ border-radius 4px
+ background isDark ? #282C37 : #fff
+ color isDark ? #fff : #303133
+ box-shadow 0 2px 12px 0 rgba(#000, 0.1)
+
+ > header
+ padding 18px 20px
+ border-bottom 1px solid isDark ? #1c2023 : #ebeef5
+
+ > div
+ padding 20px
+ color isDark ? #fff : #606266
+
> footer
position sticky
bottom 0
padding 16px
- background rgba(255, 255, 255, 0.9)
- border-top solid 1px #c4cdd4
+ background rgba(isDark ? #191b22 : #fff, 0.9)
+ border-top solid 1px isDark ? #606266 : #c4cdd4
> .status
margin 0 0 16px 0
-</style>
-
-<style lang="stylus" module>
-.mapSelect
- width 100%
-
-.board
- display grid
- grid-gap 4px
- width 300px
- height 300px
- margin 0 auto
- > div
- background transparent
- border solid 2px #ddd
- border-radius 6px
- overflow hidden
- cursor pointer
-
- *
- pointer-events none
- user-select none
- width 100%
- height 100%
+.urbixznjwwuukfsckrwzwsqzsxornqij[data-darkmode]
+ root(true)
- &[data-none]
- border-color transparent
-
-</style>
+.urbixznjwwuukfsckrwzwsqzsxornqij:not([data-darkmode])
+ root(false)
-<style lang="stylus">
-.el-alert__content
- position initial !important
</style>
diff --git a/src/client/app/common/views/components/games/reversi/reversi.vue b/src/client/app/common/views/components/games/reversi/reversi.vue
index 43f1c6656a..4169a5465a 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.vue
@@ -1,58 +1,16 @@
<template>
-<div class="mk-reversi">
+<div class="vchtoekanapleubgzioubdtmlkribzfd">
<div v-if="game">
<x-gameroom :game="game"/>
</div>
<div class="matching" v-else-if="matching">
<h1>{{ '%i18n:@matching.waiting-for%'.split('{}')[0] }}<b>{{ matching | userName }}</b>{{ '%i18n:@matching.waiting-for%'.split('{}')[1] }}<mk-ellipsis/></h1>
<div class="cancel">
- <el-button round @click="cancel">%i18n:@matching.cancel%</el-button>
+ <form-button round @click="cancel">%i18n:@matching.cancel%</form-button>
</div>
</div>
<div class="index" v-else>
- <h1>%i18n:@title%</h1>
- <p>%i18n:@sub-title%</p>
- <div class="play">
- <!--<el-button round>フリーマッチ(準備中)</el-button>-->
- <el-button type="primary" round @click="match">%i18n:@invite%</el-button>
- <details>
- <summary>%i18n:@rule%</summary>
- <div>
- <p>%i18n:@rule-desc%</p>
- <dl>
- <dt><b>%i18n:@mode-invite%</b></dt>
- <dd>%i18n:@mode-invite-desc%</dd>
- </dl>
- </div>
- </details>
- </div>
- <section v-if="invitations.length > 0">
- <h2>%i18n:@invitations%</h2>
- <div class="invitation" v-for="i in invitations" tabindex="-1" @click="accept(i)">
- <mk-avatar class="avatar" :user="i.parent"/>
- <span class="name"><b>{{ i.parent | userName }}</b></span>
- <span class="username">@{{ i.parent.username }}</span>
- <mk-time :time="i.createdAt"/>
- </div>
- </section>
- <section v-if="myGames.length > 0">
- <h2>%i18n:@my-games%</h2>
- <a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
- <mk-avatar class="avatar" :user="g.user1"/>
- <mk-avatar class="avatar" :user="g.user2"/>
- <span><b>{{ g.user1 | userName }}</b> vs <b>{{ g.user2 | userName }}</b></span>
- <span class="state">{{ g.isEnded ? '%i18n:@game-state.ended%' : '%i18n:@game-state.playing%' }}</span>
- </a>
- </section>
- <section v-if="games.length > 0">
- <h2>%i18n:@all-games%</h2>
- <a class="game" v-for="g in games" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
- <mk-avatar class="avatar" :user="g.user1"/>
- <mk-avatar class="avatar" :user="g.user2"/>
- <span><b>{{ g.user1 | userName }}</b> vs <b>{{ g.user2 | userName }}</b></span>
- <span class="state">{{ g.isEnded ? '%i18n:@game-state.ended%' : '%i18n:@game-state.playing%' }}</span>
- </a>
- </section>
+ <x-index @go="onGo" @matching="onMatching"/>
</div>
</div>
</template>
@@ -60,10 +18,12 @@
<script lang="ts">
import Vue from 'vue';
import XGameroom from './reversi.gameroom.vue';
+import XIndex from './reversi.index.vue';
export default Vue.extend({
components: {
- XGameroom
+ XGameroom,
+ XIndex
},
props: ['initGame'],
@@ -71,12 +31,7 @@ export default Vue.extend({
data() {
return {
game: null,
- games: [],
- gamesFetching: true,
- gamesMoreFetching: false,
- myGames: [],
matching: null,
- invitations: [],
connection: null,
connectionId: null,
pingClock: null
@@ -101,17 +56,6 @@ export default Vue.extend({
this.connectionId = (this as any).os.streams.reversiStream.use();
this.connection.on('matched', this.onMatched);
- this.connection.on('invited', this.onInvited);
-
- (this as any).api('games/reversi/games', {
- my: true
- }).then(games => {
- this.myGames = games;
- });
-
- (this as any).api('games/reversi/invitations').then(invitations => {
- this.invitations = this.invitations.concat(invitations);
- });
this.pingClock = setInterval(() => {
if (this.matching) {
@@ -122,17 +66,11 @@ export default Vue.extend({
}
}, 3000);
}
-
- (this as any).api('games/reversi/games').then(games => {
- this.games = games;
- this.gamesFetching = false;
- });
},
beforeDestroy() {
if (this.connection) {
this.connection.off('matched', this.onMatched);
- this.connection.off('invited', this.onInvited);
(this as any).os.streams.reversiStream.dispose(this.connectionId);
clearInterval(this.pingClock);
@@ -140,33 +78,13 @@ export default Vue.extend({
},
methods: {
- go(game) {
- (this as any).api('games/reversi/games/show', {
- gameId: game.id
- }).then(game => {
- this.matching = null;
- this.game = game;
- });
+ onGo(game) {
+ this.matching = null;
+ this.game = game;
},
- match() {
- (this as any).apis.input({
- title: '%i18n:@enter-username%'
- }).then(username => {
- (this as any).api('users/show', {
- username
- }).then(user => {
- (this as any).api('games/reversi/match', {
- userId: user.id
- }).then(res => {
- if (res == null) {
- this.matching = user;
- } else {
- this.game = res;
- }
- });
- });
- });
+ onMatching(user) {
+ this.matching = user;
},
cancel() {
@@ -188,10 +106,6 @@ export default Vue.extend({
onMatched(game) {
this.matching = null;
this.game = game;
- },
-
- onInvited(invite) {
- this.invitations.unshift(invite);
}
}
});
@@ -200,9 +114,9 @@ export default Vue.extend({
<style lang="stylus" scoped>
@import '~const.styl'
-.mk-reversi
- color #677f84
- background #fff
+root(isDark)
+ color isDark ? #fff : #677f84
+ background isDark ? #191b22 : #fff
> .matching
> h1
@@ -219,109 +133,10 @@ export default Vue.extend({
text-align center
border-top dashed 1px #c4cdd4
- > .index
- > h1
- margin 0
- padding 24px
- font-size 24px
- text-align center
- font-weight normal
- color #fff
- background linear-gradient(to bottom, #8bca3e, #d6cf31)
-
- & + p
- margin 0
- padding 12px
- margin-bottom 12px
- text-align center
- font-size 14px
- border-bottom solid 1px #d3d9dc
-
- > .play
- margin 0 auto
- padding 0 16px
- max-width 500px
- text-align center
-
- > details
- margin 8px 0
-
- > div
- padding 16px
- font-size 14px
- text-align left
- background #f5f5f5
- border-radius 8px
-
- > section
- margin 0 auto
- padding 0 16px 16px 16px
- max-width 500px
- border-top solid 1px #d3d9dc
-
- > h2
- margin 0
- padding 16px 0 8px 0
- font-size 16px
- font-weight bold
-
- .invitation
- margin 8px 0
- padding 8px
- border solid 1px #e1e5e8
- border-radius 6px
- cursor pointer
-
- *
- pointer-events none
- user-select none
-
- &:focus
- border-color $theme-color
-
- &:hover
- background #f5f5f5
-
- &:active
- background #eee
-
- > .avatar
- width 32px
- height 32px
- border-radius 100%
-
- > span
- margin 0 8px
- line-height 32px
-
- .game
- display block
- margin 8px 0
- padding 8px
- color #677f84
- border solid 1px #e1e5e8
- border-radius 6px
- cursor pointer
-
- *
- pointer-events none
- user-select none
-
- &:focus
- border-color $theme-color
-
- &:hover
- background #f5f5f5
-
- &:active
- background #eee
+.vchtoekanapleubgzioubdtmlkribzfd[data-darkmode]
+ root(true)
- > .avatar
- width 32px
- height 32px
- border-radius 100%
+.vchtoekanapleubgzioubdtmlkribzfd:not([data-darkmode])
+ root(false)
- > span
- margin 0 8px
- line-height 32px
</style>
diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts
index c18a1c3b68..422a3da050 100644
--- a/src/client/app/common/views/components/index.ts
+++ b/src/client/app/common/views/components/index.ts
@@ -37,6 +37,8 @@ import uiTextarea from './ui/textarea.vue';
import uiSwitch from './ui/switch.vue';
import uiRadio from './ui/radio.vue';
import uiSelect from './ui/select.vue';
+import formButton from './ui/form/button.vue';
+import formRadio from './ui/form/radio.vue';
Vue.component('mk-analog-clock', analogClock);
Vue.component('mk-menu', menu);
@@ -75,3 +77,5 @@ Vue.component('ui-textarea', uiTextarea);
Vue.component('ui-switch', uiSwitch);
Vue.component('ui-radio', uiRadio);
Vue.component('ui-select', uiSelect);
+Vue.component('form-button', formButton);
+Vue.component('form-radio', formRadio);
diff --git a/src/client/app/common/views/components/ui/form/button.vue b/src/client/app/common/views/components/ui/form/button.vue
new file mode 100644
index 0000000000..6e1475bc38
--- /dev/null
+++ b/src/client/app/common/views/components/ui/form/button.vue
@@ -0,0 +1,86 @@
+<template>
+<div class="nvemkhtwcnnpkdrwfcbzuwhfulejhmzg" :class="{ round, primary }">
+ <button @click="$emit('click')">
+ <slot></slot>
+ </button>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+ props: {
+ round: {
+ type: Boolean,
+ required: false,
+ default: false
+ },
+ primary: {
+ type: Boolean,
+ required: false,
+ default: false
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+ display inline-block
+
+ & + .nvemkhtwcnnpkdrwfcbzuwhfulejhmzg
+ margin-left 12px
+
+ > button
+ display inline-block
+ margin 0
+ padding 12px 20px
+ font-size 14px
+ border 1px solid isDark ? #6d727d : #dcdfe6
+ border-radius 4px
+ outline none
+ box-shadow none
+ color isDark ? #fff : #606266
+ transition 0.1s
+
+ &:hover
+ &:focus
+ color $theme-color
+ background rgba($theme-color, isDark ? 0.2 : 0.12)
+ border-color rgba($theme-color, isDark ? 0.5 : 0.3)
+
+ &:active
+ color darken($theme-color, 20%)
+ background rgba($theme-color, 0.12)
+ border-color $theme-color
+ transition all 0s
+
+ &.primary
+ > button
+ border 1px solid $theme-color
+ background $theme-color
+ color $theme-color-foreground
+
+ &:hover
+ &:focus
+ background lighten($theme-color, 20%)
+ border-color lighten($theme-color, 20%)
+
+ &:active
+ background darken($theme-color, 20%)
+ border-color darken($theme-color, 20%)
+ transition all 0s
+
+ &.round
+ > button
+ border-radius 64px
+
+.nvemkhtwcnnpkdrwfcbzuwhfulejhmzg[data-darkmode]
+ root(true)
+
+.nvemkhtwcnnpkdrwfcbzuwhfulejhmzg:not([data-darkmode])
+ root(false)
+
+</style>
diff --git a/src/client/app/common/views/components/ui/form/radio.vue b/src/client/app/common/views/components/ui/form/radio.vue
new file mode 100644
index 0000000000..831981bb3e
--- /dev/null
+++ b/src/client/app/common/views/components/ui/form/radio.vue
@@ -0,0 +1,126 @@
+<template>
+<div
+ class="uywduthvrdnlpsvsjkqigicixgyfctto"
+ :class="{ disabled, checked }"
+ :aria-checked="checked"
+ :aria-disabled="disabled"
+ @click="toggle"
+>
+ <input type="radio"
+ :disabled="disabled"
+ >
+ <span class="button">
+ <span></span>
+ </span>
+ <span class="label"><slot></slot></span>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+ model: {
+ prop: 'model',
+ event: 'change'
+ },
+ props: {
+ model: {
+ required: false
+ },
+ value: {
+ required: false
+ },
+ disabled: {
+ type: Boolean,
+ default: false
+ }
+ },
+ computed: {
+ checked(): boolean {
+ return this.model === this.value;
+ }
+ },
+ methods: {
+ toggle() {
+ this.$emit('change', this.value);
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+ display inline-flex
+ margin 0 16px 0 0
+ cursor pointer
+ transition all 0.3s
+
+ > *
+ user-select none
+
+ &:hover
+ > .button
+ border solid 2px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
+
+ &.disabled
+ opacity 0.6
+ cursor not-allowed
+
+ &.checked
+ > .button
+ border-color $theme-color
+
+ &:after
+ background-color $theme-color
+ transform scale(1)
+ opacity 1
+
+ > .label
+ color $theme-color
+
+ > input
+ position absolute
+ width 0
+ height 0
+ opacity 0
+ margin 0
+
+ > .button
+ display inline-block
+ flex-shrink 0
+ width 20px
+ height 20px
+ background none
+ border solid 2px isDark ? rgba(#fff, 0.6) : rgba(#000, 0.4)
+ border-radius 100%
+ transition inherit
+
+ &:after
+ content ''
+ display block
+ position absolute
+ top 3px
+ right 3px
+ bottom 3px
+ left 3px
+ border-radius 100%
+ opacity 0
+ transform scale(0)
+ transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
+
+ > .label
+ margin-left 8px
+ display block
+ font-size 14px
+ line-height 20px
+ cursor pointer
+
+.uywduthvrdnlpsvsjkqigicixgyfctto[data-darkmode]
+ root(true)
+
+.uywduthvrdnlpsvsjkqigicixgyfctto:not([data-darkmode])
+ root(false)
+
+</style>