From 689411c19a267630855251f7d54999250faabbf7 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Wed, 21 Dec 2022 16:00:00 +0900 Subject: [PATCH] refactor(client): refacotr MkMediaCaption --- locales/ja-JP.yml | 1 + .../client/src/components/MkDrive.file.vue | 18 +- .../components/MkFileCaptionEditWindow.vue | 175 ++++++++++++ .../client/src/components/MkMediaCaption.vue | 263 ------------------ .../src/components/MkPostFormAttaches.vue | 17 +- 5 files changed, 188 insertions(+), 286 deletions(-) create mode 100644 packages/client/src/components/MkFileCaptionEditWindow.vue delete mode 100644 packages/client/src/components/MkMediaCaption.vue diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0e8e22752f..7737e6044e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -908,6 +908,7 @@ sendPushNotificationReadMessage: "通知やメッセージが既読になった sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」という通知が一瞬表示されるようになります。端末の電池消費量が増加する可能性があります。" windowMaximize: "最大化" windowRestore: "元に戻す" +caption: "キャプション" _sensitiveMediaDetection: description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" diff --git a/packages/client/src/components/MkDrive.file.vue b/packages/client/src/components/MkDrive.file.vue index 11bd9e9e3b..8c17c0530a 100644 --- a/packages/client/src/components/MkDrive.file.vue +++ b/packages/client/src/components/MkDrive.file.vue @@ -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, - default: props.file.comment != null ? props.file.comment : '', - }, - image: props.file, + os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), { + default: props.file.comment != null ? props.file.comment : '', + 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'); diff --git a/packages/client/src/components/MkFileCaptionEditWindow.vue b/packages/client/src/components/MkFileCaptionEditWindow.vue new file mode 100644 index 0000000000..73875251f0 --- /dev/null +++ b/packages/client/src/components/MkFileCaptionEditWindow.vue @@ -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> diff --git a/packages/client/src/components/MkMediaCaption.vue b/packages/client/src/components/MkMediaCaption.vue deleted file mode 100644 index c25755d762..0000000000 --- a/packages/client/src/components/MkMediaCaption.vue +++ /dev/null @@ -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> diff --git a/packages/client/src/components/MkPostFormAttaches.vue b/packages/client/src/components/MkPostFormAttaches.vue index 955d835f25..2e16cc52a7 100644 --- a/packages/client/src/components/MkPostFormAttaches.vue +++ b/packages/client/src/components/MkPostFormAttaches.vue @@ -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, - default: file.comment !== null ? file.comment : '', - }, - image: file, + os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), { + default: file.comment !== null ? file.comment : '', + 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,