commit
						6da9da0e8f
					
				
					 8 changed files with 670 additions and 323 deletions
				
			
		| 
						 | 
				
			
			@ -178,6 +178,11 @@ auth/views/index.vue:
 | 
			
		|||
  sign-in: "サインインしてください"
 | 
			
		||||
  
 | 
			
		||||
common/views/components/games/reversi/reversi.vue:
 | 
			
		||||
  matching:
 | 
			
		||||
    waiting-for: "{}を待っています"
 | 
			
		||||
    cancel: "キャンセル"
 | 
			
		||||
 | 
			
		||||
common/views/components/games/reversi/reversi.index.vue:
 | 
			
		||||
  title: "Misskey Reversi"
 | 
			
		||||
  sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
 | 
			
		||||
  invite: "招待"
 | 
			
		||||
| 
						 | 
				
			
			@ -192,9 +197,6 @@ common/views/components/games/reversi/reversi.vue:
 | 
			
		|||
  game-state:
 | 
			
		||||
    ended: "終了"
 | 
			
		||||
    playing: "進行中"
 | 
			
		||||
  matching:
 | 
			
		||||
    waiting-for: "{}を待っています"
 | 
			
		||||
    cancel: "キャンセル"
 | 
			
		||||
 | 
			
		||||
common/views/components/games/reversi/reversi.room.vue:
 | 
			
		||||
  settings-of-the-game: "ゲームの設定"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
			</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>
 | 
			
		||||
			</header>
 | 
			
		||||
 | 
			
		||||
		<el-card class="rules">
 | 
			
		||||
			<div slot="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>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="card">
 | 
			
		||||
			<header>
 | 
			
		||||
				<span>%i18n:@rules%</span>
 | 
			
		||||
			</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>
 | 
			
		||||
			</header>
 | 
			
		||||
 | 
			
		||||
		<el-card class="bot-form" v-if="form">
 | 
			
		||||
			<div slot="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>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		<div class="card" v-if="form">
 | 
			
		||||
			<header>
 | 
			
		||||
				<span>%i18n:@settings-of-the-bot%</span>
 | 
			
		||||
			</header>
 | 
			
		||||
 | 
			
		||||
			<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>
 | 
			
		||||
 | 
			
		||||
					<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>
 | 
			
		||||
				</template>
 | 
			
		||||
			</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>
 | 
			
		||||
 | 
			
		||||
				<el-card v-if="item.type == 'radio'" :key="item.id">
 | 
			
		||||
					<div slot="header">
 | 
			
		||||
						<span>{{ item.label }}</span>
 | 
			
		||||
					</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>
 | 
			
		||||
					<el-input v-model="item.value" @change="onChangeForm($event, item)"/>
 | 
			
		||||
				</el-card>
 | 
			
		||||
			</template>
 | 
			
		||||
		</el-card>
 | 
			
		||||
		</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%
 | 
			
		||||
.urbixznjwwuukfsckrwzwsqzsxornqij[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
.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%
 | 
			
		||||
 | 
			
		||||
		&[data-none]
 | 
			
		||||
			border-color transparent
 | 
			
		||||
.urbixznjwwuukfsckrwzwsqzsxornqij:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
.el-alert__content
 | 
			
		||||
	position initial !important
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
.vchtoekanapleubgzioubdtmlkribzfd[data-darkmode]
 | 
			
		||||
	root(true)
 | 
			
		||||
 | 
			
		||||
			& + p
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 12px
 | 
			
		||||
				margin-bottom 12px
 | 
			
		||||
				text-align center
 | 
			
		||||
				font-size 14px
 | 
			
		||||
				border-bottom solid 1px #d3d9dc
 | 
			
		||||
.vchtoekanapleubgzioubdtmlkribzfd:not([data-darkmode])
 | 
			
		||||
	root(false)
 | 
			
		||||
 | 
			
		||||
		> .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
 | 
			
		||||
 | 
			
		||||
		> .avatar
 | 
			
		||||
			width 32px
 | 
			
		||||
			height 32px
 | 
			
		||||
			border-radius 100%
 | 
			
		||||
 | 
			
		||||
		> span
 | 
			
		||||
			margin 0 8px
 | 
			
		||||
			line-height 32px
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										86
									
								
								src/client/app/common/views/components/ui/form/button.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/client/app/common/views/components/ui/form/button.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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>
 | 
			
		||||
							
								
								
									
										126
									
								
								src/client/app/common/views/components/ui/form/radio.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/client/app/common/views/components/ui/form/radio.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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>
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue