refactor(client): refacotr MkMediaCaption
This commit is contained in:
		
							parent
							
								
									20fd9db77f
								
							
						
					
					
						commit
						689411c19a
					
				
					 5 changed files with 188 additions and 286 deletions
				
			
		|  | @ -908,6 +908,7 @@ sendPushNotificationReadMessage: "通知やメッセージが既読になった | |||
| sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」という通知が一瞬表示されるようになります。端末の電池消費量が増加する可能性があります。" | ||||
| windowMaximize: "最大化" | ||||
| windowRestore: "元に戻す" | ||||
| caption: "キャプション" | ||||
| 
 | ||||
| _sensitiveMediaDetection: | ||||
|   description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" | ||||
|  |  | |||
|  | @ -71,7 +71,7 @@ function getMenu() { | |||
| 		action: toggleSensitive, | ||||
| 	}, { | ||||
| 		text: i18n.ts.describeFile, | ||||
| 		icon: 'ti ti-forms', | ||||
| 		icon: 'ti ti-text-caption', | ||||
| 		action: describe, | ||||
| 	}, null, { | ||||
| 		text: i18n.ts.copyUrl, | ||||
|  | @ -134,20 +134,14 @@ function rename() { | |||
| } | ||||
| 
 | ||||
| function describe() { | ||||
| 	os.popup(defineAsyncComponent(() => import('@/components/MkMediaCaption.vue')), { | ||||
| 		title: i18n.ts.describeFile, | ||||
| 		input: { | ||||
| 			placeholder: i18n.ts.inputNewDescription, | ||||
| 	os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), { | ||||
| 		default: props.file.comment != null ? props.file.comment : '', | ||||
| 		}, | ||||
| 		image: props.file, | ||||
| 		file: props.file, | ||||
| 	}, { | ||||
| 		done: result => { | ||||
| 			if (!result || result.canceled) return; | ||||
| 			let comment = result.result; | ||||
| 		done: caption => { | ||||
| 			os.api('drive/files/update', { | ||||
| 				fileId: props.file.id, | ||||
| 				comment: comment.length === 0 ? null : comment, | ||||
| 				comment: caption.length === 0 ? null : caption, | ||||
| 			}); | ||||
| 		}, | ||||
| 	}, 'closed'); | ||||
|  |  | |||
							
								
								
									
										175
									
								
								packages/client/src/components/MkFileCaptionEditWindow.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								packages/client/src/components/MkFileCaptionEditWindow.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,175 @@ | |||
| <template> | ||||
| <XModalWindow | ||||
| 	ref="dialog" | ||||
| 	:width="400" | ||||
| 	:height="450" | ||||
| 	:with-ok-button="true" | ||||
| 	:ok-button-disabled="false" | ||||
| 	@ok="ok()" | ||||
| 	@close="dialog.close()" | ||||
| 	@closed="emit('closed')" | ||||
| > | ||||
| 	<template #header>{{ i18n.ts.describeFile }}</template> | ||||
| 	<div> | ||||
| 		<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain" style="height: 100px;"/> | ||||
| 		<MkTextarea v-model="caption" autofocus :placeholder="i18n.ts.inputNewDescription"> | ||||
| 			<template #label>{{ i18n.ts.caption }}</template> | ||||
| 		</MkTextarea> | ||||
| 	</div> | ||||
| </XModalWindow> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import XModalWindow from '@/components/MkModalWindow.vue'; | ||||
| import MkTextarea from '@/components/form/textarea.vue'; | ||||
| import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; | ||||
| import { i18n } from '@/i18n'; | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
| 	file: Misskey.entities.DriveFile; | ||||
| 	default: string; | ||||
| }>(); | ||||
| 
 | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'done', v: string): void; | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
| 
 | ||||
| const dialog = $ref<InstanceType<typeof XModalWindow>>(); | ||||
| 
 | ||||
| let caption = $ref(props.default); | ||||
| 
 | ||||
| async function ok() { | ||||
| 	emit('done', caption); | ||||
| 	dialog.close(); | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .container { | ||||
| 	display: flex; | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 	flex-direction: row; | ||||
| 	overflow: scroll; | ||||
| 	position: fixed; | ||||
| 	left: 0; | ||||
| 	top: 0; | ||||
| } | ||||
| @media (max-width: 850px) { | ||||
| 	.container { | ||||
| 		flex-direction: column; | ||||
| 	} | ||||
| 	.top-caption { | ||||
| 		padding-bottom: 8px; | ||||
| 	} | ||||
| } | ||||
| .fullwidth { | ||||
| 	width: 100%; | ||||
| 	margin: auto; | ||||
| } | ||||
| .mk-dialog { | ||||
| 	position: relative; | ||||
| 	padding: 32px; | ||||
| 	min-width: 320px; | ||||
| 	max-width: 480px; | ||||
| 	box-sizing: border-box; | ||||
| 	text-align: center; | ||||
| 	background: var(--panel); | ||||
| 	border-radius: var(--radius); | ||||
| 	margin: auto; | ||||
| 
 | ||||
| 	> header { | ||||
| 		margin: 0 0 8px 0; | ||||
| 		position: relative; | ||||
| 
 | ||||
| 		> .title { | ||||
| 			font-weight: bold; | ||||
| 			font-size: 20px; | ||||
| 		} | ||||
| 
 | ||||
| 		> .text-count { | ||||
| 			opacity: 0.7; | ||||
| 			position: absolute; | ||||
| 			right: 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .buttons { | ||||
| 		margin-top: 16px; | ||||
| 
 | ||||
| 		> * { | ||||
| 			margin: 0 8px; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> textarea { | ||||
| 		display: block; | ||||
| 		box-sizing: border-box; | ||||
| 		padding: 0 24px; | ||||
| 		margin: 0; | ||||
| 		width: 100%; | ||||
| 		font-size: 16px; | ||||
| 		border: none; | ||||
| 		border-radius: 0; | ||||
| 		background: transparent; | ||||
| 		color: var(--fg); | ||||
| 		font-family: inherit; | ||||
| 		max-width: 100%; | ||||
| 		min-width: 100%; | ||||
| 		min-height: 90px; | ||||
| 
 | ||||
| 		&:focus-visible { | ||||
| 			outline: none; | ||||
| 		} | ||||
| 
 | ||||
| 		&:disabled { | ||||
| 			opacity: 0.5; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| .hdrwpsaf { | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	height: 100%; | ||||
| 
 | ||||
| 	> header, | ||||
| 	> footer { | ||||
| 		align-self: center; | ||||
| 		display: inline-block; | ||||
| 		padding: 6px 9px; | ||||
| 		font-size: 90%; | ||||
| 		background: rgba(0, 0, 0, 0.5); | ||||
| 		border-radius: 6px; | ||||
| 		color: #fff; | ||||
| 	} | ||||
| 
 | ||||
| 	> header { | ||||
| 		margin-bottom: 8px; | ||||
| 		opacity: 0.9; | ||||
| 	} | ||||
| 
 | ||||
| 	> img { | ||||
| 		display: block; | ||||
| 		flex: 1; | ||||
| 		min-height: 0; | ||||
| 		object-fit: contain; | ||||
| 		width: 100%; | ||||
| 		cursor: zoom-out; | ||||
| 		image-orientation: from-image; | ||||
| 	} | ||||
| 
 | ||||
| 	> footer { | ||||
| 		margin-top: 8px; | ||||
| 		opacity: 0.8; | ||||
| 
 | ||||
| 		> span + span { | ||||
| 			margin-left: 0.5em; | ||||
| 			padding-left: 0.5em; | ||||
| 			border-left: solid 1px rgba(255, 255, 255, 0.5); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  | @ -1,263 +0,0 @@ | |||
| <template> | ||||
| 	<MkModal ref="modal" @click="done(true)" @closed="$emit('closed')"> | ||||
| 		<div class="container"> | ||||
| 			<div class="fullwidth top-caption"> | ||||
| 				<div class="mk-dialog"> | ||||
| 					<header> | ||||
| 						<Mfm v-if="title" class="title" :text="title"/> | ||||
| 						<span class="text-count" :class="{ over: remainingLength < 0 }">{{ remainingLength }}</span> | ||||
| 					</header> | ||||
| 					<textarea v-model="inputValue" autofocus :placeholder="input.placeholder" @keydown="onInputKeydown"></textarea> | ||||
| 					<div v-if="(showOkButton || showCancelButton)" class="buttons"> | ||||
| 						<MkButton inline primary :disabled="remainingLength < 0" @click="ok">{{ $ts.ok }}</MkButton> | ||||
| 						<MkButton inline @click="cancel" >{{ $ts.cancel }}</MkButton> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<div class="hdrwpsaf fullwidth"> | ||||
| 				<header>{{ image.name }}</header> | ||||
| 				<img :src="image.url" :alt="image.comment" :title="image.comment" @click="$refs.modal.close()"/> | ||||
| 				<footer> | ||||
| 					<span>{{ image.type }}</span> | ||||
| 					<span>{{ bytes(image.size) }}</span> | ||||
| 					<span v-if="image.properties && image.properties.width">{{ number(image.properties.width) }}px × {{ number(image.properties.height) }}px</span> | ||||
| 				</footer> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</MkModal> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { length } from 'stringz'; | ||||
| import MkModal from '@/components/MkModal.vue'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| import bytes from '@/filters/bytes'; | ||||
| import number from '@/filters/number'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkModal, | ||||
| 		MkButton, | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		image: { | ||||
| 			type: Object, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		title: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		}, | ||||
| 		input: { | ||||
| 			required: true | ||||
| 		}, | ||||
| 		showOkButton: { | ||||
| 			type: Boolean, | ||||
| 			default: true | ||||
| 		}, | ||||
| 		showCancelButton: { | ||||
| 			type: Boolean, | ||||
| 			default: true | ||||
| 		}, | ||||
| 		cancelableByBgClick: { | ||||
| 			type: Boolean, | ||||
| 			default: true | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['done', 'closed'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			inputValue: this.input.default ? this.input.default : null | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		remainingLength(): number { | ||||
| 			if (typeof this.inputValue !== "string") return 512; | ||||
| 			return 512 - length(this.inputValue); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		document.addEventListener('keydown', this.onKeydown); | ||||
| 	}, | ||||
| 
 | ||||
| 	beforeUnmount() { | ||||
| 		document.removeEventListener('keydown', this.onKeydown); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		bytes, | ||||
| 		number, | ||||
| 
 | ||||
| 		done(canceled, result?) { | ||||
| 			this.$emit('done', { canceled, result }); | ||||
| 			this.$refs.modal.close(); | ||||
| 		}, | ||||
| 
 | ||||
| 		async ok() { | ||||
| 			if (!this.showOkButton) return; | ||||
| 
 | ||||
| 			const result = this.inputValue; | ||||
| 			this.done(false, result); | ||||
| 		}, | ||||
| 
 | ||||
| 		cancel() { | ||||
| 			this.done(true); | ||||
| 		}, | ||||
| 
 | ||||
| 		onBgClick() { | ||||
| 			if (this.cancelableByBgClick) { | ||||
| 				this.cancel(); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		onKeydown(evt) { | ||||
| 			if (evt.which === 27) { // ESC | ||||
| 				this.cancel(); | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		onInputKeydown(evt) { | ||||
| 			if (evt.which === 13) { // Enter | ||||
| 				if (evt.ctrlKey) { | ||||
| 					evt.preventDefault(); | ||||
| 					evt.stopPropagation(); | ||||
| 					this.ok(); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .container { | ||||
| 	display: flex; | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 	flex-direction: row; | ||||
| 	overflow: scroll; | ||||
| 	position: fixed; | ||||
| 	left: 0; | ||||
| 	top: 0; | ||||
| } | ||||
| @media (max-width: 850px) { | ||||
| 	.container { | ||||
| 		flex-direction: column; | ||||
| 	} | ||||
| 	.top-caption { | ||||
| 		padding-bottom: 8px; | ||||
| 	} | ||||
| } | ||||
| .fullwidth { | ||||
| 	width: 100%; | ||||
| 	margin: auto; | ||||
| } | ||||
| .mk-dialog { | ||||
| 	position: relative; | ||||
| 	padding: 32px; | ||||
| 	min-width: 320px; | ||||
| 	max-width: 480px; | ||||
| 	box-sizing: border-box; | ||||
| 	text-align: center; | ||||
| 	background: var(--panel); | ||||
| 	border-radius: var(--radius); | ||||
| 	margin: auto; | ||||
| 
 | ||||
| 	> header { | ||||
| 		margin: 0 0 8px 0; | ||||
| 		position: relative; | ||||
| 
 | ||||
| 		> .title { | ||||
| 			font-weight: bold; | ||||
| 			font-size: 20px; | ||||
| 		} | ||||
| 
 | ||||
| 		> .text-count { | ||||
| 			opacity: 0.7; | ||||
| 			position: absolute; | ||||
| 			right: 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .buttons { | ||||
| 		margin-top: 16px; | ||||
| 
 | ||||
| 		> * { | ||||
| 			margin: 0 8px; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> textarea { | ||||
| 		display: block; | ||||
| 		box-sizing: border-box; | ||||
| 		padding: 0 24px; | ||||
| 		margin: 0; | ||||
| 		width: 100%; | ||||
| 		font-size: 16px; | ||||
| 		border: none; | ||||
| 		border-radius: 0; | ||||
| 		background: transparent; | ||||
| 		color: var(--fg); | ||||
| 		font-family: inherit; | ||||
| 		max-width: 100%; | ||||
| 		min-width: 100%; | ||||
| 		min-height: 90px; | ||||
| 
 | ||||
| 		&:focus-visible { | ||||
| 			outline: none; | ||||
| 		} | ||||
| 
 | ||||
| 		&:disabled { | ||||
| 			opacity: 0.5; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| .hdrwpsaf { | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	height: 100%; | ||||
| 
 | ||||
| 	> header, | ||||
| 	> footer { | ||||
| 		align-self: center; | ||||
| 		display: inline-block; | ||||
| 		padding: 6px 9px; | ||||
| 		font-size: 90%; | ||||
| 		background: rgba(0, 0, 0, 0.5); | ||||
| 		border-radius: 6px; | ||||
| 		color: #fff; | ||||
| 	} | ||||
| 
 | ||||
| 	> header { | ||||
| 		margin-bottom: 8px; | ||||
| 		opacity: 0.9; | ||||
| 	} | ||||
| 
 | ||||
| 	> img { | ||||
| 		display: block; | ||||
| 		flex: 1; | ||||
| 		min-height: 0; | ||||
| 		object-fit: contain; | ||||
| 		width: 100%; | ||||
| 		cursor: zoom-out; | ||||
| 		image-orientation: from-image; | ||||
| 	} | ||||
| 
 | ||||
| 	> footer { | ||||
| 		margin-top: 8px; | ||||
| 		opacity: 0.8; | ||||
| 
 | ||||
| 		> span + span { | ||||
| 			margin-left: 0.5em; | ||||
| 			padding-left: 0.5em; | ||||
| 			border-left: solid 1px rgba(255, 255, 255, 0.5); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  | @ -70,17 +70,12 @@ async function rename(file) { | |||
| } | ||||
| 
 | ||||
| async function describe(file) { | ||||
| 	os.popup(defineAsyncComponent(() => import('@/components/MkMediaCaption.vue')), { | ||||
| 		title: i18n.ts.describeFile, | ||||
| 		input: { | ||||
| 			placeholder: i18n.ts.inputNewDescription, | ||||
| 	os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), { | ||||
| 		default: file.comment !== null ? file.comment : '', | ||||
| 		}, | ||||
| 		image: file, | ||||
| 		file: file, | ||||
| 	}, { | ||||
| 		done: result => { | ||||
| 			if (!result || result.canceled) return; | ||||
| 			let comment = result.result.length === 0 ? null : result.result; | ||||
| 		done: caption => { | ||||
| 			let comment = caption.length === 0 ? null : caption; | ||||
| 			os.api('drive/files/update', { | ||||
| 				fileId: file.id, | ||||
| 				comment: comment, | ||||
|  | @ -103,7 +98,7 @@ function showFileMenu(file, ev: MouseEvent) { | |||
| 		action: () => { toggleSensitive(file); }, | ||||
| 	}, { | ||||
| 		text: i18n.ts.describeFile, | ||||
| 		icon: 'ti ti-forms', | ||||
| 		icon: 'ti ti-text-caption', | ||||
| 		action: () => { describe(file); }, | ||||
| 	}, { | ||||
| 		text: i18n.ts.attachCancel, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue