From bf654c6f42179612c19d70bf1970f930efc25d54 Mon Sep 17 00:00:00 2001
From: Aya Morisawa <AyaMorisawa4869@gmail.com>
Date: Tue, 29 Oct 2019 09:51:19 +0900
Subject: [PATCH] Componentize modal (#5386)

---
 .../app/common/views/components/dialog.vue    | 141 +++++++-----------
 .../common/views/components/image-viewer.vue  |  62 ++------
 .../app/common/views/components/index.ts      |   2 +
 .../app/common/views/components/ui/modal.vue  |  80 ++++++++++
 .../views/components/media-video-dialog.vue   |  55 ++-----
 .../views/components/post-form-dialog.vue     |  71 ++++-----
 6 files changed, 188 insertions(+), 223 deletions(-)
 create mode 100644 src/client/app/common/views/components/ui/modal.vue

diff --git a/src/client/app/common/views/components/dialog.vue b/src/client/app/common/views/components/dialog.vue
index d5906eb4c4..2744903007 100644
--- a/src/client/app/common/views/components/dialog.vue
+++ b/src/client/app/common/views/components/dialog.vue
@@ -1,6 +1,12 @@
 <template>
-<div class="felqjxyj" :class="{ splash }">
-	<div class="bg" ref="bg" @click="onBgClick"></div>
+<ui-modal
+	ref="modal"
+	class="modal"
+	:class="{ splash }"
+	:close-anime-duration="300"
+	:close-on-bg-click="false"
+	@bg-click="onBgClick"
+	@before-close="onBeforeClose">
 	<div class="main" ref="main" :class="{ round: $store.state.device.roundedCorners }">
 		<template v-if="type == 'signin'">
 			<mk-signin/>
@@ -38,7 +44,7 @@
 			</ui-horizon-group>
 		</template>
 	</div>
-</div>
+</ui-modal>
 </template>
 
 <script lang="ts">
@@ -120,14 +126,6 @@ export default Vue.extend({
 		if (this.user) this.canOk = false;
 
 		this.$nextTick(() => {
-			(this.$refs.bg as any).style.pointerEvents = 'auto';
-			anime({
-				targets: this.$refs.bg,
-				opacity: 1,
-				duration: 100,
-				easing: 'linear'
-			});
-
 			anime({
 				targets: this.$refs.main,
 				opacity: 1,
@@ -170,33 +168,27 @@ export default Vue.extend({
 			this.close();
 		},
 
+		onBgClick() {
+			if (this.cancelableByBgClick) this.cancel();
+		}
+
 		close() {
+			this.$refs.modal.close();
+		},
+
+		onBeforeClose() {
 			this.$el.style.pointerEvents = 'none';
-			(this.$refs.bg as any).style.pointerEvents = 'none';
 			(this.$refs.main as any).style.pointerEvents = 'none';
 
-			anime({
-				targets: this.$refs.bg,
-				opacity: 0,
-				duration: 300,
-				easing: 'linear'
-			});
 			anime({
 				targets: this.$refs.main,
 				opacity: 0,
 				scale: 0.8,
 				duration: 300,
 				easing: 'cubicBezier(0, 0.5, 0.5, 1)',
-				complete: () => this.destroyDom()
 			});
 		},
 
-		onBgClick() {
-			if (this.cancelableByBgClick) {
-				this.cancel();
-			}
-		},
-
 		onInputKeydown(e) {
 			if (e.which == 13) { // Enter
 				e.preventDefault();
@@ -209,80 +201,63 @@ export default Vue.extend({
 </script>
 
 <style lang="stylus" scoped>
-.felqjxyj
+.modal
 	display flex
 	align-items center
 	justify-content center
-	position fixed
-	z-index 30000
-	top 0
-	left 0
-	width 100%
-	height 100%
 
 	&.splash
 		> .main
 			min-width 0
 			width initial
 
-	> .bg
-		display block
-		position fixed
-		top 0
-		left 0
-		width 100%
-		height 100%
-		background rgba(#000, 0.7)
-		opacity 0
-		pointer-events none
+.main
+	display block
+	position fixed
+	margin auto
+	padding 32px
+	min-width 320px
+	max-width 480px
+	width calc(100% - 32px)
+	text-align center
+	background var(--face)
+	color var(--faceText)
+	opacity 0
 
-	> .main
-		display block
-		position fixed
-		margin auto
-		padding 32px
-		min-width 320px
-		max-width 480px
-		width calc(100% - 32px)
-		text-align center
-		background var(--face)
-		color var(--faceText)
-		opacity 0
+	&.round
+		border-radius 8px
 
-		&.round
-			border-radius 8px
+	> .icon
+		font-size 32px
 
-		> .icon
-			font-size 32px
+		&.success
+			color #85da5a
 
-			&.success
-				color #85da5a
+		&.error
+			color #ec4137
 
-			&.error
-				color #ec4137
+		&.warning
+			color #ecb637
 
-			&.warning
-				color #ecb637
+		> *
+			display block
+			margin 0 auto
 
-			> *
-				display block
-				margin 0 auto
-
-			& + header
-				margin-top 16px
-
-		> header
-			margin 0 0 8px 0
-			font-weight bold
-			font-size 20px
-
-			& + .body
-				margin-top 8px
-
-		> .body
-			margin 16px 0 0 0
-
-		> .buttons
+		& + header
 			margin-top 16px
 
+	> header
+		margin 0 0 8px 0
+		font-weight bold
+		font-size 20px
+
+		& + .body
+			margin-top 8px
+
+	> .body
+		margin 16px 0 0 0
+
+	> .buttons
+		margin-top 16px
+
 </style>
diff --git a/src/client/app/common/views/components/image-viewer.vue b/src/client/app/common/views/components/image-viewer.vue
index 7787942ca8..63b5e28d00 100644
--- a/src/client/app/common/views/components/image-viewer.vue
+++ b/src/client/app/common/views/components/image-viewer.vue
@@ -1,24 +1,14 @@
 <template>
-<div class="dkjvrdxtkvqrwmhfickhndpmnncsgacq" v-hotkey.global="keymap">
-	<div class="bg" @click="close"></div>
-	<img :src="image.url" :alt="image.name" :title="image.name" @click="close"/>
-</div>
+<ui-modal ref="modal" v-hotkey.global="keymap">
+	<img :src="image.url" :alt="image.name" :title="image.name" @click="close" />
+</ui-modal>
 </template>
 
 <script lang="ts">
 import Vue from 'vue';
-import anime from 'animejs';
 
 export default Vue.extend({
 	props: ['image'],
-	mounted() {
-		anime({
-			targets: this.$el,
-			opacity: 1,
-			duration: 100,
-			easing: 'linear'
-		});
-	},
 	computed: {
 		keymap(): any {
 			return {
@@ -28,50 +18,24 @@ export default Vue.extend({
 	},
 	methods: {
 		close() {
-			anime({
-				targets: this.$el,
-				opacity: 0,
-				duration: 100,
-				easing: 'linear',
-				complete: () => this.destroyDom()
-			});
+			(this.$refs.modal as any).close();
 		}
 	}
 });
 </script>
 
 <style lang="stylus" scoped>
-.dkjvrdxtkvqrwmhfickhndpmnncsgacq
-	display block
+img
 	position fixed
-	z-index 2048
+	z-index 2
 	top 0
+	right 0
+	bottom 0
 	left 0
-	width 100%
-	height 100%
-	opacity 0
-
-	> .bg
-		display block
-		position fixed
-		z-index 1
-		top 0
-		left 0
-		width 100%
-		height 100%
-		background rgba(#000, 0.7)
-
-	> img
-		position fixed
-		z-index 2
-		top 0
-		right 0
-		bottom 0
-		left 0
-		max-width 100%
-		max-height 100%
-		margin auto
-		cursor zoom-out
-		image-orientation from-image
+	max-width 100%
+	max-height 100%
+	margin auto
+	cursor zoom-out
+	image-orientation from-image
 
 </style>
diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts
index 4253118ba8..88cd4931d4 100644
--- a/src/client/app/common/views/components/index.ts
+++ b/src/client/app/common/views/components/index.ts
@@ -47,6 +47,7 @@ import uiInfo from './ui/info.vue';
 import uiMargin from './ui/margin.vue';
 import uiHr from './ui/hr.vue';
 import uiPagination from './ui/pagination.vue';
+import uiModal from './ui/modal.vue';
 import formButton from './ui/form/button.vue';
 import formRadio from './ui/form/radio.vue';
 
@@ -97,5 +98,6 @@ Vue.component('ui-info', uiInfo);
 Vue.component('ui-margin', uiMargin);
 Vue.component('ui-hr', uiHr);
 Vue.component('ui-pagination', uiPagination);
+Vue.component('ui-modal', uiModal);
 Vue.component('form-button', formButton);
 Vue.component('form-radio', formRadio);
diff --git a/src/client/app/common/views/components/ui/modal.vue b/src/client/app/common/views/components/ui/modal.vue
new file mode 100644
index 0000000000..413dc39fa5
--- /dev/null
+++ b/src/client/app/common/views/components/ui/modal.vue
@@ -0,0 +1,80 @@
+<template>
+<div class="modal">
+	<div class="bg" ref="bg" @click="onBgClick" />
+	<slot class="main" />
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import anime from 'animejs';
+
+export default Vue.extend({
+	props: {
+		closeOnBgClick: {
+			type: Boolean,
+			required: false,
+			default: true
+		},
+		openAnimeDuration: {
+			type: Number,
+			required: false,
+			default: 100
+		},
+		closeAnimeDuration: {
+			type: Number,
+			required: false,
+			default: 100
+		}
+	},
+	mounted() {
+		anime({
+			targets: this.$refs.bg,
+			opacity: 1,
+			duration: this.openAnimeDuration,
+			easing: 'linear'
+		});
+	},
+	methods: {
+		onBgClick() {
+			this.$emit('bg-click');
+			if (this.closeOnBgClick) this.close();
+		},
+		close() {
+			this.$emit('before-close');
+
+			anime({
+				targets: this.$refs.bg,
+				opacity: 0,
+				duration: this.closeAnimeDuration,
+				easing: 'linear',
+				complete: () => (this as any).destroyDom()
+			});
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+.modal
+	position fixed
+	z-index 2048
+	top 0
+	left 0
+	width 100%
+	height 100%
+
+.bg
+	display block
+	position fixed
+	z-index 1
+	top 0
+	left 0
+	width 100%
+	height 100%
+	background rgba(#000, 0.7)
+	opacity 0
+
+.main
+	z-index 1
+</style>
diff --git a/src/client/app/desktop/views/components/media-video-dialog.vue b/src/client/app/desktop/views/components/media-video-dialog.vue
index 803350506a..9d2d0527ef 100644
--- a/src/client/app/desktop/views/components/media-video-dialog.vue
+++ b/src/client/app/desktop/views/components/media-video-dialog.vue
@@ -1,23 +1,15 @@
 <template>
-<div class="mk-media-video-dialog" v-hotkey.global="keymap">
-	<div class="bg" @click="close"></div>
-	<video :src="video.url" :title="video.name" controls autoplay ref="video" @volumechange="volumechange"/>
-</div>
+<ui-modal v-hotkey.global="keymap">
+	<video :src="video.url" :title="video.name" controls autoplay ref="video" @volumechange="volumechange" />
+</ui-modal>
 </template>
 
 <script lang="ts">
 import Vue from 'vue';
-import anime from 'animejs';
 
 export default Vue.extend({
 	props: ['video', 'start'],
 	mounted() {
-		anime({
-			targets: this.$el,
-			opacity: 1,
-			duration: 100,
-			easing: 'linear'
-		});
 		const videoTag = this.$refs.video as HTMLVideoElement;
 		if (this.start) videoTag.currentTime = this.start
 		videoTag.volume = this.$store.state.device.mediaVolume;
@@ -31,13 +23,6 @@ export default Vue.extend({
 	},
 	methods: {
 		close() {
-			anime({
-				targets: this.$el,
-				opacity: 0,
-				duration: 100,
-				easing: 'linear',
-				complete: () => this.destroyDom()
-			});
 		},
 		volumechange() {
 			const videoTag = this.$refs.video as HTMLVideoElement;
@@ -48,35 +33,15 @@ export default Vue.extend({
 </script>
 
 <style lang="stylus" scoped>
-.mk-media-video-dialog
-	display block
+video
 	position fixed
-	z-index 2048
+	z-index 2
 	top 0
+	right 0
+	bottom 0
 	left 0
-	width 100%
-	height 100%
-	opacity 0
-
-	> .bg
-		display block
-		position fixed
-		z-index 1
-		top 0
-		left 0
-		width 100%
-		height 100%
-		background rgba(#000, 0.7)
-
-	> video
-		position fixed
-		z-index 2
-		top 0
-		right 0
-		bottom 0
-		left 0
-		max-width 80vw
-		max-height 80vh
-		margin auto
+	max-width 80vw
+	max-height 80vh
+	margin auto
 
 </style>
diff --git a/src/client/app/mobile/views/components/post-form-dialog.vue b/src/client/app/mobile/views/components/post-form-dialog.vue
index 716ad8fd07..4ae79dbd7b 100644
--- a/src/client/app/mobile/views/components/post-form-dialog.vue
+++ b/src/client/app/mobile/views/components/post-form-dialog.vue
@@ -1,6 +1,9 @@
 <template>
-<div class="ulveipglmagnxfgvitaxyszerjwiqmwl">
-	<div class="bg" ref="bg"></div>
+<ui-modal
+	ref="modal"
+	:close-on-bg-click="false"
+	:close-anime-duration="300"
+	@before-close="onBeforeClose">
 	<div class="main" ref="main">
 		<x-post-form ref="form"
 			:reply="reply"
@@ -12,7 +15,7 @@
 			@posted="onPosted"
 			@cancel="onCanceled"/>
 	</div>
-</div>
+</ui-modal>
 </template>
 
 <script lang="ts">
@@ -55,14 +58,6 @@ export default Vue.extend({
 
 	mounted() {
 		this.$nextTick(() => {
-			(this.$refs.bg as any).style.pointerEvents = 'auto';
-			anime({
-				targets: this.$refs.bg,
-				opacity: 1,
-				duration: 100,
-				easing: 'linear'
-			});
-
 			anime({
 				targets: this.$refs.main,
 				opacity: 1,
@@ -78,26 +73,22 @@ export default Vue.extend({
 			this.$refs.form.focus();
 		},
 
-		close() {
-			(this.$refs.bg as any).style.pointerEvents = 'none';
-			anime({
-				targets: this.$refs.bg,
-				opacity: 0,
-				duration: 300,
-				easing: 'linear'
-			});
-
+		onBeforeClose() {
 			(this.$refs.main as any).style.pointerEvents = 'none';
+
 			anime({
 				targets: this.$refs.main,
 				opacity: 0,
 				translateY: 16,
 				duration: 300,
-				easing: 'easeOutQuad',
-				complete: () => this.destroyDom()
+				easing: 'easeOutQuad'
 			});
 		},
 
+		close() {
+			(this.$refs.modal as any).close();
+		},
+
 		onPosted() {
 			this.$emit('posted');
 			this.close();
@@ -112,30 +103,18 @@ export default Vue.extend({
 </script>
 
 <style lang="stylus" scoped>
-.ulveipglmagnxfgvitaxyszerjwiqmwl
-	> .bg
-		display block
-		position fixed
-		z-index 10000
-		top 0
-		left 0
-		width 100%
-		height 100%
-		background rgba(#000, 0.7)
-		opacity 0
-		pointer-events none
 
-	> .main
-		display block
-		position fixed
-		z-index 10000
-		top 0
-		left 0
-		right 0
-		height 100%
-		overflow auto
-		margin 0 auto 0 auto
-		opacity 0
-		transform translateY(-16px)
+.main
+	display block
+	position fixed
+	z-index 10000
+	top 0
+	left 0
+	right 0
+	height 100%
+	overflow auto
+	margin 0 auto 0 auto
+	opacity 0
+	transform translateY(-16px)
 
 </style>