parent
							
								
									1838511766
								
							
						
					
					
						commit
						ecb3c43520
					
				
					 9 changed files with 420 additions and 94 deletions
				
			
		|  | @ -16,6 +16,7 @@ You should also include the user name that made the change. | ||||||
| - プッシュ通知にクリックやactionを設定 #7667 @tamaina | - プッシュ通知にクリックやactionを設定 #7667 @tamaina | ||||||
| - ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina | - ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina | ||||||
| - Server: always remove completed tasks of job queue @Johann150 | - Server: always remove completed tasks of job queue @Johann150 | ||||||
|  | - Client: アバターの設定で画像をクロップできるように @syuilo | ||||||
| - Client: make emoji stand out more on reaction button @Johann150 | - Client: make emoji stand out more on reaction button @Johann150 | ||||||
| - Client: display URL of QR code for TOTP registration @tamaina | - Client: display URL of QR code for TOTP registration @tamaina | ||||||
| - Client: render quote renote CWs as MFM @pixeldesu | - Client: render quote renote CWs as MFM @pixeldesu | ||||||
|  |  | ||||||
|  | @ -843,6 +843,8 @@ oneWeek: "1週間" | ||||||
| reflectMayTakeTime: "反映されるまで時間がかかる場合があります。" | reflectMayTakeTime: "反映されるまで時間がかかる場合があります。" | ||||||
| failedToFetchAccountInformation: "アカウント情報の取得に失敗しました" | failedToFetchAccountInformation: "アカウント情報の取得に失敗しました" | ||||||
| rateLimitExceeded: "レート制限を超えました" | rateLimitExceeded: "レート制限を超えました" | ||||||
|  | cropImage: "画像のクロップ" | ||||||
|  | cropImageAsk: "画像をクロップしますか?" | ||||||
| 
 | 
 | ||||||
| _emailUnavailable: | _emailUnavailable: | ||||||
|   used: "既に使用されています" |   used: "既に使用されています" | ||||||
|  |  | ||||||
|  | @ -12,7 +12,11 @@ | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@discordapp/twemoji": "14.0.2", | 		"@discordapp/twemoji": "14.0.2", | ||||||
| 		"@fortawesome/fontawesome-free": "6.1.1", | 		"@fortawesome/fontawesome-free": "6.1.1", | ||||||
|  | 		"@rollup/plugin-alias": "3.1.9", | ||||||
|  | 		"@rollup/plugin-json": "4.1.0", | ||||||
| 		"@syuilo/aiscript": "0.11.1", | 		"@syuilo/aiscript": "0.11.1", | ||||||
|  | 		"@vitejs/plugin-vue": "2.3.3", | ||||||
|  | 		"@vue/compiler-sfc": "3.2.37", | ||||||
| 		"abort-controller": "3.0.0", | 		"abort-controller": "3.0.0", | ||||||
| 		"autobind-decorator": "2.4.0", | 		"autobind-decorator": "2.4.0", | ||||||
| 		"autosize": "5.0.1", | 		"autosize": "5.0.1", | ||||||
|  | @ -26,6 +30,7 @@ | ||||||
| 		"chartjs-plugin-zoom": "1.2.1", | 		"chartjs-plugin-zoom": "1.2.1", | ||||||
| 		"compare-versions": "4.1.3", | 		"compare-versions": "4.1.3", | ||||||
| 		"content-disposition": "0.5.4", | 		"content-disposition": "0.5.4", | ||||||
|  | 		"cropperjs": "2.0.0-beta", | ||||||
| 		"date-fns": "2.28.0", | 		"date-fns": "2.28.0", | ||||||
| 		"escape-regexp": "0.0.1", | 		"escape-regexp": "0.0.1", | ||||||
| 		"eventemitter3": "4.0.7", | 		"eventemitter3": "4.0.7", | ||||||
|  | @ -51,6 +56,7 @@ | ||||||
| 		"random-seed": "0.3.0", | 		"random-seed": "0.3.0", | ||||||
| 		"reflect-metadata": "0.1.13", | 		"reflect-metadata": "0.1.13", | ||||||
| 		"rndstr": "1.0.0", | 		"rndstr": "1.0.0", | ||||||
|  | 		"rollup": "2.75.6", | ||||||
| 		"s-age": "1.1.2", | 		"s-age": "1.1.2", | ||||||
| 		"sass": "1.52.3", | 		"sass": "1.52.3", | ||||||
| 		"seedrandom": "3.0.5", | 		"seedrandom": "3.0.5", | ||||||
|  | @ -64,21 +70,16 @@ | ||||||
| 		"tsc-alias": "1.6.9", | 		"tsc-alias": "1.6.9", | ||||||
| 		"tsconfig-paths": "4.0.0", | 		"tsconfig-paths": "4.0.0", | ||||||
| 		"twemoji-parser": "14.0.0", | 		"twemoji-parser": "14.0.0", | ||||||
|  | 		"typescript": "4.7.3", | ||||||
| 		"uuid": "8.3.2", | 		"uuid": "8.3.2", | ||||||
| 		"v-debounce": "0.1.2", | 		"v-debounce": "0.1.2", | ||||||
| 		"vanilla-tilt": "1.7.2", | 		"vanilla-tilt": "1.7.2", | ||||||
|  | 		"vite": "2.9.10", | ||||||
| 		"vue": "3.2.37", | 		"vue": "3.2.37", | ||||||
| 		"vue-prism-editor": "2.0.0-alpha.2", | 		"vue-prism-editor": "2.0.0-alpha.2", | ||||||
| 		"vue-router": "4.0.16", | 		"vue-router": "4.0.16", | ||||||
| 		"vuedraggable": "4.0.1", | 		"vuedraggable": "4.0.1", | ||||||
| 		"websocket": "1.0.34", | 		"websocket": "1.0.34", | ||||||
| 		"@vitejs/plugin-vue": "2.3.3", |  | ||||||
| 		"@vue/compiler-sfc": "3.2.37", |  | ||||||
| 		"@rollup/plugin-alias": "3.1.9", |  | ||||||
| 		"@rollup/plugin-json": "4.1.0", |  | ||||||
| 		"rollup": "2.75.6", |  | ||||||
| 		"typescript": "4.7.3", |  | ||||||
| 		"vite": "2.9.10", |  | ||||||
| 		"ws": "8.8.0" | 		"ws": "8.8.0" | ||||||
| 	}, | 	}, | ||||||
| 	"devDependencies": { | 	"devDependencies": { | ||||||
|  | @ -102,11 +103,11 @@ | ||||||
| 		"@types/ws": "8.5.3", | 		"@types/ws": "8.5.3", | ||||||
| 		"@typescript-eslint/eslint-plugin": "5.27.1", | 		"@typescript-eslint/eslint-plugin": "5.27.1", | ||||||
| 		"@typescript-eslint/parser": "5.27.1", | 		"@typescript-eslint/parser": "5.27.1", | ||||||
| 		"eslint": "8.17.0", |  | ||||||
| 		"eslint-plugin-vue": "9.1.0", |  | ||||||
| 		"cross-env": "7.0.3", | 		"cross-env": "7.0.3", | ||||||
| 		"cypress": "10.0.3", | 		"cypress": "10.0.3", | ||||||
|  | 		"eslint": "8.17.0", | ||||||
| 		"eslint-plugin-import": "2.26.0", | 		"eslint-plugin-import": "2.26.0", | ||||||
|  | 		"eslint-plugin-vue": "9.1.0", | ||||||
| 		"start-server-and-test": "1.14.0" | 		"start-server-and-test": "1.14.0" | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										171
									
								
								packages/client/src/components/cropper-dialog.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								packages/client/src/components/cropper-dialog.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,171 @@ | ||||||
|  | <template> | ||||||
|  | <XModalWindow | ||||||
|  | 	ref="dialogEl" | ||||||
|  | 	:width="800" | ||||||
|  | 	:height="500" | ||||||
|  | 	:scroll="false" | ||||||
|  | 	:with-ok-button="true" | ||||||
|  | 	@close="cancel()" | ||||||
|  | 	@ok="ok()" | ||||||
|  | 	@closed="$emit('closed')" | ||||||
|  | > | ||||||
|  | 	<template #header>{{ $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="file.url" 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/ui/modal-window.vue'; | ||||||
|  | import * as os from '@/os'; | ||||||
|  | import { $i } from '@/account'; | ||||||
|  | import { defaultStore } from '@/store'; | ||||||
|  | import { apiUrl } from '@/config'; | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | 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); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			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> | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| <template> | <template> | ||||||
| <MkModal ref="modal" :prefer-type="'dialog'" @click="$emit('click')" @closed="$emit('closed')"> | <MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')"> | ||||||
| 	<div class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown"> | 	<div ref="rootEl" class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown"> | ||||||
| 		<div class="header"> | 		<div ref="headerEl" class="header"> | ||||||
| 			<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button> | 			<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button> | ||||||
| 			<span class="title"> | 			<span class="title"> | ||||||
| 				<slot name="header"></slot> | 				<slot name="header"></slot> | ||||||
|  | @ -11,82 +11,82 @@ | ||||||
| 		</div> | 		</div> | ||||||
| 		<div v-if="padding" class="body"> | 		<div v-if="padding" class="body"> | ||||||
| 			<div class="_section"> | 			<div class="_section"> | ||||||
| 				<slot></slot> | 				<slot :width="bodyWidth" :height="bodyHeight"></slot> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div v-else class="body"> | 		<div v-else class="body"> | ||||||
| 			<slot></slot> | 			<slot :width="bodyWidth" :height="bodyHeight"></slot> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| </MkModal> | </MkModal> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts" setup> | ||||||
| import { defineComponent } from 'vue'; | import { onMounted, onUnmounted } from 'vue'; | ||||||
| import MkModal from './modal.vue'; | import MkModal from './modal.vue'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | const props = withDefaults(defineProps<{ | ||||||
| 	components: { | 	withOkButton: boolean; | ||||||
| 		MkModal | 	okButtonDisabled: boolean; | ||||||
| 	}, | 	padding: boolean; | ||||||
| 	props: { | 	width: number; | ||||||
| 		withOkButton: { | 	height: number | null; | ||||||
| 			type: Boolean, | 	scroll: boolean; | ||||||
| 			required: false, | }>(), { | ||||||
| 			default: false | 	withOkButton: false, | ||||||
| 		}, | 	okButtonDisabled: false, | ||||||
| 		okButtonDisabled: { | 	padding: false, | ||||||
| 			type: Boolean, | 	width: 400, | ||||||
| 			required: false, | 	height: null, | ||||||
| 			default: false | 	scroll: true, | ||||||
| 		}, | }); | ||||||
| 		padding: { |  | ||||||
| 			type: Boolean, |  | ||||||
| 			required: false, |  | ||||||
| 			default: false |  | ||||||
| 		}, |  | ||||||
| 		width: { |  | ||||||
| 			type: Number, |  | ||||||
| 			required: false, |  | ||||||
| 			default: 400 |  | ||||||
| 		}, |  | ||||||
| 		height: { |  | ||||||
| 			type: Number, |  | ||||||
| 			required: false, |  | ||||||
| 			default: null |  | ||||||
| 		}, |  | ||||||
| 		canClose: { |  | ||||||
| 			type: Boolean, |  | ||||||
| 			required: false, |  | ||||||
| 			default: true, |  | ||||||
| 		}, |  | ||||||
| 		scroll: { |  | ||||||
| 			type: Boolean, |  | ||||||
| 			required: false, |  | ||||||
| 			default: true, |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 
 | 
 | ||||||
| 	emits: ['click', 'close', 'closed', 'ok'], | const emit = defineEmits<{ | ||||||
|  | 	(event: 'click'): void; | ||||||
|  | 	(event: 'close'): void; | ||||||
|  | 	(event: 'closed'): void; | ||||||
|  | 	(event: 'ok'): void; | ||||||
|  | }>(); | ||||||
| 
 | 
 | ||||||
| 	data() { | let modal = $ref<InstanceType<typeof MkModal>>(); | ||||||
| 		return { | let rootEl = $ref<HTMLElement>(); | ||||||
| 		}; | let headerEl = $ref<HTMLElement>(); | ||||||
| 	}, | let bodyWidth = $ref(0); | ||||||
|  | let bodyHeight = $ref(0); | ||||||
| 
 | 
 | ||||||
| 	methods: { | const close = () => { | ||||||
| 		close() { | 	modal.close(); | ||||||
| 			this.$refs.modal.close(); | }; | ||||||
| 		}, |  | ||||||
| 
 | 
 | ||||||
| 		onKeydown(evt) { | const onBgClick = () => { | ||||||
|  | 	emit('click'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const onKeydown = (evt) => { | ||||||
| 	if (evt.which === 27) { // Esc | 	if (evt.which === 27) { // Esc | ||||||
| 		evt.preventDefault(); | 		evt.preventDefault(); | ||||||
| 		evt.stopPropagation(); | 		evt.stopPropagation(); | ||||||
| 				this.close(); | 		close(); | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 	} | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const ro = new ResizeObserver((entries, observer) => { | ||||||
|  | 	bodyWidth = rootEl.offsetWidth; | ||||||
|  | 	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  | 	bodyWidth = rootEl.offsetWidth; | ||||||
|  | 	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight; | ||||||
|  | 	ro.observe(rootEl); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | onUnmounted(() => { | ||||||
|  | 	ro.disconnect(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | defineExpose({ | ||||||
|  | 	close, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <template> | <template> | ||||||
| <transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="childRendered"> | <transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened"> | ||||||
| 	<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> | 	<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> | ||||||
| 		<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div> | 		<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div> | ||||||
| 		<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick"> | 		<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick"> | ||||||
|  | @ -48,6 +48,7 @@ const props = withDefaults(defineProps<{ | ||||||
| 
 | 
 | ||||||
| const emit = defineEmits<{ | const emit = defineEmits<{ | ||||||
| 	(ev: 'opening'): void; | 	(ev: 'opening'): void; | ||||||
|  | 	(ev: 'opened'): void; | ||||||
| 	(ev: 'click'): void; | 	(ev: 'click'): void; | ||||||
| 	(ev: 'esc'): void; | 	(ev: 'esc'): void; | ||||||
| 	(ev: 'close'): void; | 	(ev: 'close'): void; | ||||||
|  | @ -212,7 +213,9 @@ const align = () => { | ||||||
| 	popover.style.top = top + 'px'; | 	popover.style.top = top + 'px'; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const childRendered = () => { | const onOpened = () => { | ||||||
|  | 	emit('opened'); | ||||||
|  | 
 | ||||||
| 	// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する | 	// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する | ||||||
| 	const el = content.value!.children[0]; | 	const el = content.value!.children[0]; | ||||||
| 	el.addEventListener('mousedown', ev => { | 	el.addEventListener('mousedown', ev => { | ||||||
|  | @ -237,7 +240,7 @@ onMounted(() => { | ||||||
| 		await nextTick(); | 		await nextTick(); | ||||||
| 		 | 		 | ||||||
| 		align(); | 		align(); | ||||||
| 	}, { immediate: true, }); | 	}, { immediate: true }); | ||||||
| 
 | 
 | ||||||
| 	nextTick(() => { | 	nextTick(() => { | ||||||
| 		const popover = content.value; | 		const popover = content.value; | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s | ||||||
| 			method: 'POST', | 			method: 'POST', | ||||||
| 			body: JSON.stringify(data), | 			body: JSON.stringify(data), | ||||||
| 			credentials: 'omit', | 			credentials: 'omit', | ||||||
| 			cache: 'no-cache' | 			cache: 'no-cache', | ||||||
| 		}).then(async (res) => { | 		}).then(async (res) => { | ||||||
| 			const body = res.status === 204 ? null : await res.json(); | 			const body = res.status === 204 ? null : await res.json(); | ||||||
| 
 | 
 | ||||||
|  | @ -142,7 +142,7 @@ export async function popup(component: Component, props: Record<string, any>, ev | ||||||
| 		props, | 		props, | ||||||
| 		events: disposeEvent ? { | 		events: disposeEvent ? { | ||||||
| 			...events, | 			...events, | ||||||
| 			[disposeEvent]: dispose | 			[disposeEvent]: dispose, | ||||||
| 		} : events, | 		} : events, | ||||||
| 		id, | 		id, | ||||||
| 	}; | 	}; | ||||||
|  | @ -174,7 +174,7 @@ export function modalPageWindow(path: string) { | ||||||
| 
 | 
 | ||||||
| export function toast(message: string) { | export function toast(message: string) { | ||||||
| 	popup(defineAsyncComponent(() => import('@/components/toast.vue')), { | 	popup(defineAsyncComponent(() => import('@/components/toast.vue')), { | ||||||
| 		message | 		message, | ||||||
| 	}, {}, 'closed'); | 	}, {}, 'closed'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -226,7 +226,7 @@ export function inputText(props: { | ||||||
| 				type: props.type, | 				type: props.type, | ||||||
| 				placeholder: props.placeholder, | 				placeholder: props.placeholder, | ||||||
| 				default: props.default, | 				default: props.default, | ||||||
| 			} | 			}, | ||||||
| 		}, { | 		}, { | ||||||
| 			done: result => { | 			done: result => { | ||||||
| 				resolve(result ? result : { canceled: true }); | 				resolve(result ? result : { canceled: true }); | ||||||
|  | @ -251,7 +251,7 @@ export function inputNumber(props: { | ||||||
| 				type: 'number', | 				type: 'number', | ||||||
| 				placeholder: props.placeholder, | 				placeholder: props.placeholder, | ||||||
| 				default: props.default, | 				default: props.default, | ||||||
| 			} | 			}, | ||||||
| 		}, { | 		}, { | ||||||
| 			done: result => { | 			done: result => { | ||||||
| 				resolve(result ? result : { canceled: true }); | 				resolve(result ? result : { canceled: true }); | ||||||
|  | @ -276,7 +276,7 @@ export function inputDate(props: { | ||||||
| 				type: 'date', | 				type: 'date', | ||||||
| 				placeholder: props.placeholder, | 				placeholder: props.placeholder, | ||||||
| 				default: props.default, | 				default: props.default, | ||||||
| 			} | 			}, | ||||||
| 		}, { | 		}, { | ||||||
| 			done: result => { | 			done: result => { | ||||||
| 				resolve(result ? { result: new Date(result.result), canceled: false } : { canceled: true }); | 				resolve(result ? { result: new Date(result.result), canceled: false } : { canceled: true }); | ||||||
|  | @ -313,7 +313,7 @@ export function select<C = any>(props: { | ||||||
| 				items: props.items, | 				items: props.items, | ||||||
| 				groupedItems: props.groupedItems, | 				groupedItems: props.groupedItems, | ||||||
| 				default: props.default, | 				default: props.default, | ||||||
| 			} | 			}, | ||||||
| 		}, { | 		}, { | ||||||
| 			done: result => { | 			done: result => { | ||||||
| 				resolve(result ? result : { canceled: true }); | 				resolve(result ? result : { canceled: true }); | ||||||
|  | @ -330,7 +330,7 @@ export function success() { | ||||||
| 		}, 1000); | 		}, 1000); | ||||||
| 		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { | 		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { | ||||||
| 			success: true, | 			success: true, | ||||||
| 			showing: showing | 			showing: showing, | ||||||
| 		}, { | 		}, { | ||||||
| 			done: () => resolve(), | 			done: () => resolve(), | ||||||
| 		}, 'closed'); | 		}, 'closed'); | ||||||
|  | @ -342,7 +342,7 @@ export function waiting() { | ||||||
| 		const showing = ref(true); | 		const showing = ref(true); | ||||||
| 		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { | 		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { | ||||||
| 			success: false, | 			success: false, | ||||||
| 			showing: showing | 			showing: showing, | ||||||
| 		}, { | 		}, { | ||||||
| 			done: () => resolve(), | 			done: () => resolve(), | ||||||
| 		}, 'closed'); | 		}, 'closed'); | ||||||
|  | @ -373,7 +373,7 @@ export async function selectDriveFile(multiple: boolean) { | ||||||
| 	return new Promise((resolve, reject) => { | 	return new Promise((resolve, reject) => { | ||||||
| 		popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), { | 		popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), { | ||||||
| 			type: 'file', | 			type: 'file', | ||||||
| 			multiple | 			multiple, | ||||||
| 		}, { | 		}, { | ||||||
| 			done: files => { | 			done: files => { | ||||||
| 				if (files) { | 				if (files) { | ||||||
|  | @ -388,7 +388,7 @@ export async function selectDriveFolder(multiple: boolean) { | ||||||
| 	return new Promise((resolve, reject) => { | 	return new Promise((resolve, reject) => { | ||||||
| 		popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), { | 		popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), { | ||||||
| 			type: 'folder', | 			type: 'folder', | ||||||
| 			multiple | 			multiple, | ||||||
| 		}, { | 		}, { | ||||||
| 			done: folders => { | 			done: folders => { | ||||||
| 				if (folders) { | 				if (folders) { | ||||||
|  | @ -403,7 +403,7 @@ export async function pickEmoji(src: HTMLElement | null, opts) { | ||||||
| 	return new Promise((resolve, reject) => { | 	return new Promise((resolve, reject) => { | ||||||
| 		popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), { | 		popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), { | ||||||
| 			src, | 			src, | ||||||
| 			...opts | 			...opts, | ||||||
| 		}, { | 		}, { | ||||||
| 			done: emoji => { | 			done: emoji => { | ||||||
| 				resolve(emoji); | 				resolve(emoji); | ||||||
|  | @ -412,6 +412,21 @@ export async function pickEmoji(src: HTMLElement | null, opts) { | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export async function cropImage(image: Misskey.entities.DriveFile, options: { | ||||||
|  | 	aspectRatio: number; | ||||||
|  | }): Promise<Misskey.entities.DriveFile> { | ||||||
|  | 	return new Promise((resolve, reject) => { | ||||||
|  | 		popup(defineAsyncComponent(() => import('@/components/cropper-dialog.vue')), { | ||||||
|  | 			file: image, | ||||||
|  | 			aspectRatio: options.aspectRatio, | ||||||
|  | 		}, { | ||||||
|  | 			ok: x => { | ||||||
|  | 				resolve(x); | ||||||
|  | 			}, | ||||||
|  | 		}, 'closed'); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type AwaitType<T> = | type AwaitType<T> = | ||||||
| 	T extends Promise<infer U> ? U : | 	T extends Promise<infer U> ? U : | ||||||
| 	T extends (...args: any[]) => Promise<infer V> ? V : | 	T extends (...args: any[]) => Promise<infer V> ? V : | ||||||
|  | @ -453,7 +468,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: | ||||||
| 
 | 
 | ||||||
| 	openingEmojiPicker = await popup(defineAsyncComponent(() => import('@/components/emoji-picker-window.vue')), { | 	openingEmojiPicker = await popup(defineAsyncComponent(() => import('@/components/emoji-picker-window.vue')), { | ||||||
| 		src, | 		src, | ||||||
| 		...opts | 		...opts, | ||||||
| 	}, { | 	}, { | ||||||
| 		chosen: emoji => { | 		chosen: emoji => { | ||||||
| 			insertTextAtCursor(activeTextarea, emoji); | 			insertTextAtCursor(activeTextarea, emoji); | ||||||
|  | @ -462,7 +477,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: | ||||||
| 			openingEmojiPicker!.dispose(); | 			openingEmojiPicker!.dispose(); | ||||||
| 			openingEmojiPicker = null; | 			openingEmojiPicker = null; | ||||||
| 			observer.disconnect(); | 			observer.disconnect(); | ||||||
| 		} | 		}, | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -478,7 +493,7 @@ export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement | ||||||
| 			src, | 			src, | ||||||
| 			width: options?.width, | 			width: options?.width, | ||||||
| 			align: options?.align, | 			align: options?.align, | ||||||
| 			viaKeyboard: options?.viaKeyboard | 			viaKeyboard: options?.viaKeyboard, | ||||||
| 		}, { | 		}, { | ||||||
| 			closed: () => { | 			closed: () => { | ||||||
| 				resolve(); | 				resolve(); | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { defineComponent, reactive, watch } from 'vue'; | import { reactive, watch } from 'vue'; | ||||||
| import MkButton from '@/components/ui/button.vue'; | import MkButton from '@/components/ui/button.vue'; | ||||||
| import FormInput from '@/components/form/input.vue'; | import FormInput from '@/components/form/input.vue'; | ||||||
| import FormTextarea from '@/components/form/textarea.vue'; | import FormTextarea from '@/components/form/textarea.vue'; | ||||||
|  | @ -132,8 +132,21 @@ function save() { | ||||||
| 
 | 
 | ||||||
| function changeAvatar(ev) { | function changeAvatar(ev) { | ||||||
| 	selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => { | 	selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => { | ||||||
|  | 		let originalOrCropped = file; | ||||||
|  | 
 | ||||||
|  | 		const { canceled } = await os.confirm({ | ||||||
|  | 			type: 'question', | ||||||
|  | 			text: i18n.t('cropImageAsk'), | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		if (!canceled) { | ||||||
|  | 			originalOrCropped = await os.cropImage(file, { | ||||||
|  | 				aspectRatio: 1, | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		const i = await os.apiWithDialog('i/update', { | 		const i = await os.apiWithDialog('i/update', { | ||||||
| 			avatarId: file.id, | 			avatarId: originalOrCropped.id, | ||||||
| 		}); | 		}); | ||||||
| 		$i.avatarId = i.avatarId; | 		$i.avatarId = i.avatarId; | ||||||
| 		$i.avatarUrl = i.avatarUrl; | 		$i.avatarUrl = i.avatarUrl; | ||||||
|  | @ -142,8 +155,21 @@ function changeAvatar(ev) { | ||||||
| 
 | 
 | ||||||
| function changeBanner(ev) { | function changeBanner(ev) { | ||||||
| 	selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => { | 	selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => { | ||||||
|  | 		let originalOrCropped = file; | ||||||
|  | 
 | ||||||
|  | 		const { canceled } = await os.confirm({ | ||||||
|  | 			type: 'question', | ||||||
|  | 			text: i18n.t('cropImageAsk'), | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		if (!canceled) { | ||||||
|  | 			originalOrCropped = await os.cropImage(file, { | ||||||
|  | 				aspectRatio: 2, | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		const i = await os.apiWithDialog('i/update', { | 		const i = await os.apiWithDialog('i/update', { | ||||||
| 			bannerId: file.id, | 			bannerId: originalOrCropped.id, | ||||||
| 		}); | 		}); | ||||||
| 		$i.bannerId = i.bannerId; | 		$i.bannerId = i.bannerId; | ||||||
| 		$i.bannerUrl = i.bannerUrl; | 		$i.bannerUrl = i.bannerUrl; | ||||||
|  |  | ||||||
|  | @ -40,6 +40,105 @@ | ||||||
|     lodash "^4.17.19" |     lodash "^4.17.19" | ||||||
|     to-fast-properties "^2.0.0" |     to-fast-properties "^2.0.0" | ||||||
| 
 | 
 | ||||||
|  | "@cropper/element-canvas@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/element-canvas/-/element-canvas-2.0.0-beta.tgz#9501e6a2512a78c7503f2974b1fc65f90c7fecca" | ||||||
|  |   integrity sha512-cKbox0AsUx3pMCjT7mQZx3i5FoZTR/Lzz9awuRR8/EciViMN4KkfodGHWSUrIX3zSr0fECsrb2CyNKV8DKZdpQ== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/element" "^2.0.0-beta" | ||||||
|  |     "@cropper/utils" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
|  | "@cropper/element-crosshair@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/element-crosshair/-/element-crosshair-2.0.0-beta.tgz#9d6ee1e6ed90196b6d4d2425f84909b83ffc66df" | ||||||
|  |   integrity sha512-V58xxH3+8TrT9PrUzNouRhcyucyX/xBV5hBv03g0zCu09C5p0BZjrhaPo3hkt8oQvnhYT9SbMTe+k5hIoZgkbQ== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/element" "^2.0.0-beta" | ||||||
|  |     "@cropper/utils" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
|  | "@cropper/element-grid@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/element-grid/-/element-grid-2.0.0-beta.tgz#af6f3fce213307403ad83d9935839bde39c9beeb" | ||||||
|  |   integrity sha512-F+qVLrjuHjJbaut1Gd6qSruMqYOHudhDB/r0dcLtnRW4b1yPd/QyhM5F0KLtCX7Lh6GUvpz2V9Vb/EYQLZuOkw== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/element" "^2.0.0-beta" | ||||||
|  |     "@cropper/utils" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
|  | "@cropper/element-handle@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/element-handle/-/element-handle-2.0.0-beta.tgz#bd55667e133df402616d44a694110fd0e61eef0b" | ||||||
|  |   integrity sha512-Ty12mLpiUM8XRGQN0lRNB7TKP5SOXbTWaW2Uvli1Tu3Y6iLTtXUvs2VZ/fGR8XvhB7v7Lvo+OPfzuxIRx4gwKg== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/element" "^2.0.0-beta" | ||||||
|  |     "@cropper/utils" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
|  | "@cropper/element-image@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/element-image/-/element-image-2.0.0-beta.tgz#170dbdfbeef75de2f2c0089d4739ad980d69390a" | ||||||
|  |   integrity sha512-CrHEMBo5svjj72qePBPGV4ut70RTI6n5U2k2YKcZihHSNU2h6SUEx8zkN8lNIgelsv2Bpb/PvSd1eu26BrJbtA== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/element" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-canvas" "^2.0.0-beta" | ||||||
|  |     "@cropper/utils" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
|  | "@cropper/element-selection@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/element-selection/-/element-selection-2.0.0-beta.tgz#7e1e498773bc26bb09ddaf09b0cafbe5b359ed7b" | ||||||
|  |   integrity sha512-MEK+pn2Bma5cXf1N9mC3fRKNvzi6Aj9V2TdhaCl6KdOn6Bp10a+SR8y555MXd80zzFAU/eR1e7TMTyJiPRJFcw== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/element" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-canvas" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-image" "^2.0.0-beta" | ||||||
|  |     "@cropper/utils" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
|  | "@cropper/element-shade@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/element-shade/-/element-shade-2.0.0-beta.tgz#55400aec3e352d959a706bfff1b82afca955d33e" | ||||||
|  |   integrity sha512-vfKTTkRFio/bi0ueIbdyg2ukhS35/ufsgA13dfzOgkyUT/TUsqTLONNJA2fxO0WLKSajTtvrl1ShdrSXE+EKCQ== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/element" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-canvas" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-selection" "^2.0.0-beta" | ||||||
|  |     "@cropper/utils" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
|  | "@cropper/element-viewer@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/element-viewer/-/element-viewer-2.0.0-beta.tgz#9a83b670f5cc667d7fc0071f08a1476817e0ed4e" | ||||||
|  |   integrity sha512-ZsqdOWJ8OIrK1JR00ibmYrvVMYQVFXOudXezYtf8C5lc7DdtN4elmjVOfLQQM2kxG0WvflIVo6oqqyOzFnsAFg== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/element" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-canvas" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-image" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-selection" "^2.0.0-beta" | ||||||
|  |     "@cropper/utils" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
|  | "@cropper/element@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/element/-/element-2.0.0-beta.tgz#7833a92471a16e8860530e10658add42e8781959" | ||||||
|  |   integrity sha512-seS8oDe2+Vpsy+yyqUIHzjIP6WUQRxwhFjLml/s2e+L6jF9o+g0KHzLJkBCV/ASKBnyb00aLjAt0dBXPLW/KgQ== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/utils" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
|  | "@cropper/elements@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/elements/-/elements-2.0.0-beta.tgz#e73a4edaeff7e41dcca8d096bd1bc2bdc6a376e9" | ||||||
|  |   integrity sha512-Huyptek2Q6141fRiuejhOyec/viX4zmUeMnpi+5h7OBuorTYUowZ823mmfgBZ4bb7+VPdAl79vUECV9EYq/ciw== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/element" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-canvas" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-crosshair" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-grid" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-handle" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-image" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-selection" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-shade" "^2.0.0-beta" | ||||||
|  |     "@cropper/element-viewer" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
|  | "@cropper/utils@^2.0.0-beta": | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/@cropper/utils/-/utils-2.0.0-beta.tgz#7290b03c8c1dc7a2f33406c8aecc80b339425f0e" | ||||||
|  |   integrity sha512-Bb3hCyHK2w0l0i8OtRw6C9Q5ytUC5qN+l+kx7F3GiAAFZMX7jGyfPB0uLiZ2TwDm5mosnWjyLVXmCGDcTUnYaQ== | ||||||
|  | 
 | ||||||
| "@cypress/request@^2.88.10": | "@cypress/request@^2.88.10": | ||||||
|   version "2.88.10" |   version "2.88.10" | ||||||
|   resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" |   resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" | ||||||
|  | @ -1132,6 +1231,14 @@ core-util-is@1.0.2: | ||||||
|   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" |   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" | ||||||
|   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= |   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= | ||||||
| 
 | 
 | ||||||
|  | cropperjs@2.0.0-beta: | ||||||
|  |   version "2.0.0-beta" | ||||||
|  |   resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-2.0.0-beta.tgz#bf3f9c19c426657d63c1e6dd55f635546ccec0a5" | ||||||
|  |   integrity sha512-mwupI1Ct84PUynnC9S7KenCtgXiuRYAfLwzxPlJwc392iNX8fZUPP6a8gEpmRQTgvsE9Ubme1tXLM6/HLXksiQ== | ||||||
|  |   dependencies: | ||||||
|  |     "@cropper/elements" "^2.0.0-beta" | ||||||
|  |     "@cropper/utils" "^2.0.0-beta" | ||||||
|  | 
 | ||||||
| cross-env@7.0.3: | cross-env@7.0.3: | ||||||
|   version "7.0.3" |   version "7.0.3" | ||||||
|   resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" |   resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue