summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components/MkCropperDialog.vue
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2022-12-27 14:36:33 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2022-12-27 14:36:33 +0900
commit9384f5399da39e53855beb8e7f8ded1aa56bf72e (patch)
treece5959571a981b9c4047da3c7b3fd080aa44222c /packages/frontend/src/components/MkCropperDialog.vue
parentwip: retention for dashboard (diff)
downloadsharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.gz
sharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.bz2
sharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.zip
rename: client -> frontend
Diffstat (limited to 'packages/frontend/src/components/MkCropperDialog.vue')
-rw-r--r--packages/frontend/src/components/MkCropperDialog.vue174
1 files changed, 174 insertions, 0 deletions
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
new file mode 100644
index 0000000000..ae18160dea
--- /dev/null
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -0,0 +1,174 @@
+<template>
+<XModalWindow
+ ref="dialogEl"
+ :width="800"
+ :height="500"
+ :scroll="false"
+ :with-ok-button="true"
+ @close="cancel()"
+ @ok="ok()"
+ @closed="$emit('closed')"
+>
+ <template #header>{{ i18n.ts.cropImage }}</template>
+ <template #default="{ width, height }">
+ <div class="mk-cropper-dialog" :style="`--vw: ${width}px; --vh: ${height}px;`">
+ <Transition name="fade">
+ <div v-if="loading" class="loading">
+ <MkLoading/>
+ </div>
+ </Transition>
+ <div class="container">
+ <img ref="imgEl" :src="imgUrl" style="display: none;" @load="onImageLoad">
+ </div>
+ </div>
+ </template>
+</XModalWindow>
+</template>
+
+<script lang="ts" setup>
+import { nextTick, onMounted } from 'vue';
+import * as misskey from 'misskey-js';
+import Cropper from 'cropperjs';
+import tinycolor from 'tinycolor2';
+import XModalWindow from '@/components/MkModalWindow.vue';
+import * as os from '@/os';
+import { $i } from '@/account';
+import { defaultStore } from '@/store';
+import { apiUrl } from '@/config';
+import { i18n } from '@/i18n';
+import { getProxiedImageUrl } from '@/scripts/media-proxy';
+
+const emit = defineEmits<{
+ (ev: 'ok', cropped: misskey.entities.DriveFile): void;
+ (ev: 'cancel'): void;
+ (ev: 'closed'): void;
+}>();
+
+const props = defineProps<{
+ file: misskey.entities.DriveFile;
+ aspectRatio: number;
+}>();
+
+const imgUrl = getProxiedImageUrl(props.file.url);
+let dialogEl = $ref<InstanceType<typeof XModalWindow>>();
+let imgEl = $ref<HTMLImageElement>();
+let cropper: Cropper | null = null;
+let loading = $ref(true);
+
+const ok = async () => {
+ const promise = new Promise<misskey.entities.DriveFile>(async (res) => {
+ const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas();
+ croppedCanvas.toBlob(blob => {
+ const formData = new FormData();
+ formData.append('file', blob);
+ formData.append('i', $i.token);
+ if (defaultStore.state.uploadFolder) {
+ formData.append('folderId', defaultStore.state.uploadFolder);
+ }
+
+ window.fetch(apiUrl + '/drive/files/create', {
+ method: 'POST',
+ body: formData,
+ })
+ .then(response => response.json())
+ .then(f => {
+ res(f);
+ });
+ });
+ });
+
+ os.promiseDialog(promise);
+
+ const f = await promise;
+
+ emit('ok', f);
+ dialogEl.close();
+};
+
+const cancel = () => {
+ emit('cancel');
+ dialogEl.close();
+};
+
+const onImageLoad = () => {
+ loading = false;
+
+ if (cropper) {
+ cropper.getCropperImage()!.$center('contain');
+ cropper.getCropperSelection()!.$center();
+ }
+};
+
+onMounted(() => {
+ cropper = new Cropper(imgEl, {
+ });
+
+ const computedStyle = getComputedStyle(document.documentElement);
+
+ const selection = cropper.getCropperSelection()!;
+ selection.themeColor = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
+ selection.aspectRatio = props.aspectRatio;
+ selection.initialAspectRatio = props.aspectRatio;
+ selection.outlined = true;
+
+ window.setTimeout(() => {
+ cropper.getCropperImage()!.$center('contain');
+ selection.$center();
+ }, 100);
+
+ // モーダルオープンアニメーションが終わったあとで再度調整
+ window.setTimeout(() => {
+ cropper.getCropperImage()!.$center('contain');
+ selection.$center();
+ }, 500);
+});
+</script>
+
+<style lang="scss" scoped>
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.5s ease 0.5s;
+}
+.fade-enter-from,
+.fade-leave-to {
+ opacity: 0;
+}
+
+.mk-cropper-dialog {
+ display: flex;
+ flex-direction: column;
+ width: var(--vw);
+ height: var(--vh);
+ position: relative;
+
+ > .loading {
+ position: absolute;
+ z-index: 10;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ -webkit-backdrop-filter: var(--blur, blur(10px));
+ backdrop-filter: var(--blur, blur(10px));
+ background: rgba(0, 0, 0, 0.5);
+ }
+
+ > .container {
+ flex: 1;
+ width: 100%;
+ height: 100%;
+
+ > ::v-deep(cropper-canvas) {
+ width: 100%;
+ height: 100%;
+
+ > cropper-selection > cropper-handle[action="move"] {
+ background: transparent;
+ }
+ }
+ }
+}
+</style>