parent
							
								
									705d40ab37
								
							
						
					
					
						commit
						3f71b14637
					
				
					 22 changed files with 249 additions and 214 deletions
				
			
		
							
								
								
									
										14
									
								
								migration/1595075960584-blurhash.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								migration/1595075960584-blurhash.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | import {MigrationInterface, QueryRunner} from "typeorm"; | ||||||
|  | 
 | ||||||
|  | export class blurhash1595075960584 implements MigrationInterface { | ||||||
|  |     name = 'blurhash1595075960584' | ||||||
|  | 
 | ||||||
|  |     public async up(queryRunner: QueryRunner): Promise<void> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "drive_file" ADD "blurhash" character varying(128)`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async down(queryRunner: QueryRunner): Promise<void> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "blurhash"`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								migration/1595077605646-blurhash-for-avatar-banner.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								migration/1595077605646-blurhash-for-avatar-banner.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | import {MigrationInterface, QueryRunner} from "typeorm"; | ||||||
|  | 
 | ||||||
|  | export class blurhashForAvatarBanner1595077605646 implements MigrationInterface { | ||||||
|  |     name = 'blurhashForAvatarBanner1595077605646' | ||||||
|  | 
 | ||||||
|  |     public async up(queryRunner: QueryRunner): Promise<void> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarColor"`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerColor"`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user" ADD "avatarBlurhash" character varying(128)`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user" ADD "bannerBlurhash" character varying(128)`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public async down(queryRunner: QueryRunner): Promise<void> { | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerBlurhash"`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarBlurhash"`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user" ADD "bannerColor" character varying(32)`); | ||||||
|  |         await queryRunner.query(`ALTER TABLE "user" ADD "avatarColor" character varying(32)`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -112,6 +112,7 @@ | ||||||
| 		"autwh": "0.1.0", | 		"autwh": "0.1.0", | ||||||
| 		"aws-sdk": "2.713.0", | 		"aws-sdk": "2.713.0", | ||||||
| 		"bcryptjs": "2.4.3", | 		"bcryptjs": "2.4.3", | ||||||
|  | 		"blurhash": "1.1.3", | ||||||
| 		"bull": "3.15.0", | 		"bull": "3.15.0", | ||||||
| 		"cafy": "15.2.1", | 		"cafy": "15.2.1", | ||||||
| 		"cbor": "5.0.2", | 		"cbor": "5.0.2", | ||||||
|  |  | ||||||
|  | @ -1,15 +1,9 @@ | ||||||
| <template> | <template> | ||||||
| <span class="eiwwqkts" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick"> | <span class="eiwwqkts" :class="{ cat }" :title="user | acct" v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" @click="onClick"> | ||||||
| 	<span class="inner" :style="icon"></span> | 	<img class="inner" :src="url"/> | ||||||
| </span> | </span> | ||||||
| <span class="eiwwqkts" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick"> | <router-link class="eiwwqkts" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else v-user-preview="disablePreview ? undefined : user.id"> | ||||||
| 	<span class="inner" :style="icon"></span> | 	<img class="inner" :src="url"/> | ||||||
| </span> |  | ||||||
| <router-link class="eiwwqkts" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id"> |  | ||||||
| 	<span class="inner" :style="icon"></span> |  | ||||||
| </router-link> |  | ||||||
| <router-link class="eiwwqkts" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview"> |  | ||||||
| 	<span class="inner" :style="icon"></span> |  | ||||||
| </router-link> | </router-link> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  | @ -45,22 +39,6 @@ export default Vue.extend({ | ||||||
| 				? getStaticImageUrl(this.user.avatarUrl) | 				? getStaticImageUrl(this.user.avatarUrl) | ||||||
| 				: this.user.avatarUrl; | 				: this.user.avatarUrl; | ||||||
| 		}, | 		}, | ||||||
| 		icon(): any { |  | ||||||
| 			return { |  | ||||||
| 				backgroundColor: this.user.avatarColor, |  | ||||||
| 				backgroundImage: `url(${this.url})`, |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	watch: { |  | ||||||
| 		'user.avatarColor'() { |  | ||||||
| 			this.$el.style.color = this.user.avatarColor; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	mounted() { |  | ||||||
| 		if (this.user.avatarColor) { |  | ||||||
| 			this.$el.style.color = this.user.avatarColor; |  | ||||||
| 		} |  | ||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| 		onClick(e) { | 		onClick(e) { | ||||||
|  | @ -102,15 +80,17 @@ export default Vue.extend({ | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	.inner { | 	.inner { | ||||||
| 		background-position: center center; | 		position: absolute; | ||||||
| 		background-size: cover; |  | ||||||
| 		bottom: 0; | 		bottom: 0; | ||||||
| 		left: 0; | 		left: 0; | ||||||
| 		position: absolute; |  | ||||||
| 		right: 0; | 		right: 0; | ||||||
| 		top: 0; | 		top: 0; | ||||||
| 		border-radius: 100%; | 		border-radius: 100%; | ||||||
| 		z-index: 1; | 		z-index: 1; | ||||||
|  | 		overflow: hidden; | ||||||
|  | 		object-fit: cover; | ||||||
|  | 		width: 100%; | ||||||
|  | 		height: 100%; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,36 +1,15 @@ | ||||||
| <template> | <template> | ||||||
| <div class="zdjebgpv" :class="{ detail }" ref="thumbnail" :style="`background-color: ${ background }`"> | <div class="zdjebgpv" ref="thumbnail"> | ||||||
| 	<img | 	<img-with-blurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :style="`object-fit: ${ fit }`"/> | ||||||
| 		:src="file.url" |  | ||||||
| 		:alt="file.name" |  | ||||||
| 		:title="file.name" |  | ||||||
| 		@load="onThumbnailLoaded" |  | ||||||
| 		v-if="detail && is === 'image'"/> |  | ||||||
| 	<video |  | ||||||
| 		:src="file.url" |  | ||||||
| 		ref="volumectrl" |  | ||||||
| 		preload="metadata" |  | ||||||
| 		controls |  | ||||||
| 		v-else-if="detail && is === 'video'"/> |  | ||||||
| 	<img :src="file.thumbnailUrl" @load="onThumbnailLoaded" :style="`object-fit: ${ fit }`" v-else-if="isThumbnailAvailable"/> |  | ||||||
| 	<fa :icon="faFileImage" class="icon" v-else-if="is === 'image'"/> | 	<fa :icon="faFileImage" class="icon" v-else-if="is === 'image'"/> | ||||||
| 	<fa :icon="faFileVideo" class="icon" v-else-if="is === 'video'"/> | 	<fa :icon="faFileVideo" class="icon" v-else-if="is === 'video'"/> | ||||||
| 
 |  | ||||||
| 	<audio |  | ||||||
| 		:src="file.url" |  | ||||||
| 		ref="volumectrl" |  | ||||||
| 		preload="metadata" |  | ||||||
| 		controls |  | ||||||
| 		v-else-if="detail && is === 'audio'"/> |  | ||||||
| 	<fa :icon="faMusic" class="icon" v-else-if="is === 'audio' || is === 'midi'"/> | 	<fa :icon="faMusic" class="icon" v-else-if="is === 'audio' || is === 'midi'"/> | ||||||
| 
 |  | ||||||
| 	<fa :icon="faFileCsv" class="icon" v-else-if="is === 'csv'"/> | 	<fa :icon="faFileCsv" class="icon" v-else-if="is === 'csv'"/> | ||||||
| 	<fa :icon="faFilePdf" class="icon" v-else-if="is === 'pdf'"/> | 	<fa :icon="faFilePdf" class="icon" v-else-if="is === 'pdf'"/> | ||||||
| 	<fa :icon="faFileAlt" class="icon" v-else-if="is === 'textfile'"/> | 	<fa :icon="faFileAlt" class="icon" v-else-if="is === 'textfile'"/> | ||||||
| 	<fa :icon="faFileArchive" class="icon" v-else-if="is === 'archive'"/> | 	<fa :icon="faFileArchive" class="icon" v-else-if="is === 'archive'"/> | ||||||
| 	<fa :icon="faFile" class="icon" v-else/> | 	<fa :icon="faFile" class="icon" v-else/> | ||||||
| 
 | 	<fa :icon="faFilm" class="icon-sub" v-if="isThumbnailAvailable && is === 'video'"/> | ||||||
| 	<fa :icon="faFilm" class="icon-sub" v-if="!detail && isThumbnailAvailable && is === 'video'"/> |  | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
|  | @ -47,8 +26,12 @@ import { | ||||||
| 	faFileArchive, | 	faFileArchive, | ||||||
| 	faFilm | 	faFilm | ||||||
| 	} from '@fortawesome/free-solid-svg-icons'; | 	} from '@fortawesome/free-solid-svg-icons'; | ||||||
|  | import ImgWithBlurhash from './img-with-blurhash.vue'; | ||||||
| 
 | 
 | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
|  | 	components: { | ||||||
|  | 		ImgWithBlurhash | ||||||
|  | 	}, | ||||||
| 	props: { | 	props: { | ||||||
| 		file: { | 		file: { | ||||||
| 			type: Object, | 			type: Object, | ||||||
|  | @ -59,11 +42,6 @@ export default Vue.extend({ | ||||||
| 			required: false, | 			required: false, | ||||||
| 			default: 'cover' | 			default: 'cover' | ||||||
| 		}, | 		}, | ||||||
| 		detail: { |  | ||||||
| 			type: Boolean, |  | ||||||
| 			required: false, |  | ||||||
| 			default: false |  | ||||||
| 		} |  | ||||||
| 	}, | 	}, | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
|  | @ -108,20 +86,12 @@ export default Vue.extend({ | ||||||
| 				? (this.is === 'image' || this.is === 'video') | 				? (this.is === 'image' || this.is === 'video') | ||||||
| 				: false; | 				: false; | ||||||
| 		}, | 		}, | ||||||
| 		background(): string { |  | ||||||
| 			return this.file.properties.avgColor || 'transparent'; |  | ||||||
| 		} |  | ||||||
| 	}, | 	}, | ||||||
| 	mounted() { | 	mounted() { | ||||||
| 		const audioTag = this.$refs.volumectrl as HTMLAudioElement; | 		const audioTag = this.$refs.volumectrl as HTMLAudioElement; | ||||||
| 		if (audioTag) audioTag.volume = this.$store.state.device.mediaVolume; | 		if (audioTag) audioTag.volume = this.$store.state.device.mediaVolume; | ||||||
| 	}, | 	}, | ||||||
| 	methods: { | 	methods: { | ||||||
| 		onThumbnailLoaded() { |  | ||||||
| 			if (this.file.properties.avgColor) { |  | ||||||
| 				this.$refs.thumbnail.style.backgroundColor = 'transparent'; |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		volumechange() { | 		volumechange() { | ||||||
| 			const audioTag = this.$refs.volumectrl as HTMLAudioElement; | 			const audioTag = this.$refs.volumectrl as HTMLAudioElement; | ||||||
| 			this.$store.commit('device/set', { key: 'mediaVolume', value: audioTag.volume }); | 			this.$store.commit('device/set', { key: 'mediaVolume', value: audioTag.volume }); | ||||||
|  | @ -132,14 +102,8 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .zdjebgpv { | .zdjebgpv { | ||||||
| 	display: flex; |  | ||||||
| 	position: relative; | 	position: relative; | ||||||
| 
 | 
 | ||||||
| 	> img, |  | ||||||
| 	> .icon { |  | ||||||
| 		pointer-events: none; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	> .icon-sub { | 	> .icon-sub { | ||||||
| 		position: absolute; | 		position: absolute; | ||||||
| 		width: 30%; | 		width: 30%; | ||||||
|  | @ -153,37 +117,10 @@ export default Vue.extend({ | ||||||
| 		margin: auto; | 		margin: auto; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	&:not(.detail) { | 	> .icon { | ||||||
| 		> img { | 		pointer-events: none; | ||||||
| 			height: 100%; | 		height: 65%; | ||||||
| 			width: 100%; | 		width: 65%; | ||||||
| 			object-fit: cover; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> .icon { |  | ||||||
| 			height: 65%; |  | ||||||
| 			width: 65%; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> video, |  | ||||||
| 		> audio { |  | ||||||
| 			width: 100%; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	&.detail { |  | ||||||
| 		> .icon { |  | ||||||
| 			height: 100px; |  | ||||||
| 			width: 100px; |  | ||||||
| 			margin: 16px; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> *:not(.icon) { |  | ||||||
| 			max-height: 300px; |  | ||||||
| 			max-width: 100%; |  | ||||||
| 			height: 100%; |  | ||||||
| 			object-fit: contain; |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -126,17 +126,6 @@ export default Vue.extend({ | ||||||
| 			this.browser.isDragSource = false; | 			this.browser.isDragSource = false; | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| 		onThumbnailLoaded() { |  | ||||||
| 			if (this.file.properties.avgColor) { |  | ||||||
| 				anime({ |  | ||||||
| 					targets: this.$refs.thumbnail, |  | ||||||
| 					backgroundColor: 'transparent', // TODO fade |  | ||||||
| 					duration: 100, |  | ||||||
| 					easing: 'linear' |  | ||||||
| 				}); |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		rename() { | 		rename() { | ||||||
| 			this.$root.dialog({ | 			this.$root.dialog({ | ||||||
| 				title: this.$t('renameFile'), | 				title: this.$t('renameFile'), | ||||||
|  | @ -332,7 +321,6 @@ export default Vue.extend({ | ||||||
| 		width: 128px; | 		width: 128px; | ||||||
| 		height: 128px; | 		height: 128px; | ||||||
| 		margin: auto; | 		margin: auto; | ||||||
| 		color: var(--driveFileIcon); |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	> .name { | 	> .name { | ||||||
|  |  | ||||||
							
								
								
									
										78
									
								
								src/client/components/img-with-blurhash.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/client/components/img-with-blurhash.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,78 @@ | ||||||
|  | <template> | ||||||
|  | <div class="xubzgfgb" :title="title"> | ||||||
|  | 	<canvas ref="canvas" :width="size" :height="size" :title="title" v-if="!loaded"/> | ||||||
|  | 	<img v-if="src" :src="src" :title="title" :alt="alt" @load="onLoad"/> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import { decode } from 'blurhash'; | ||||||
|  | 
 | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	props: { | ||||||
|  | 		src: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: false, | ||||||
|  | 			default: null | ||||||
|  | 		}, | ||||||
|  | 		hash: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: true | ||||||
|  | 		}, | ||||||
|  | 		alt: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: false, | ||||||
|  | 			default: '', | ||||||
|  | 		}, | ||||||
|  | 		title: { | ||||||
|  | 			type: String, | ||||||
|  | 			required: false, | ||||||
|  | 			default: null, | ||||||
|  | 		}, | ||||||
|  | 		size: { | ||||||
|  | 			type: Number, | ||||||
|  | 			required: false, | ||||||
|  | 			default: 64 | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			loaded: false, | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	mounted() { | ||||||
|  | 		this.draw(); | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
|  | 	methods: { | ||||||
|  | 		draw() { | ||||||
|  | 			const pixels = decode(this.hash, this.size, this.size); | ||||||
|  | 			const ctx = (this.$refs.canvas as HTMLCanvasElement).getContext('2d'); | ||||||
|  | 			const imageData = ctx!.createImageData(this.size, this.size); | ||||||
|  | 			imageData.data.set(pixels); | ||||||
|  | 			ctx!.putImageData(imageData, 0, 0); | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		onLoad() { | ||||||
|  | 			this.loaded = true; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | .xubzgfgb { | ||||||
|  | 	width: 100%; | ||||||
|  | 	height: 100%; | ||||||
|  | 
 | ||||||
|  | 	> canvas, | ||||||
|  | 	> img { | ||||||
|  | 		width: 100%; | ||||||
|  | 		height: 100%; | ||||||
|  | 		object-fit: cover; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -1,19 +1,22 @@ | ||||||
| <template> | <template> | ||||||
| <div class="qjewsnkgzzxlxtzncydssfbgjibiehcy" v-if="hide" @click="hide = false"> | <div class="qjewsnkg" v-if="hide" @click="hide = false"> | ||||||
| 	<div> | 	<img-with-blurhash class="bg" :hash="image.blurhash" :title="image.name"/> | ||||||
| 		<b><fa :icon="faExclamationTriangle"/> {{ $t('sensitive') }}</b> | 	<div class="text"> | ||||||
| 		<span>{{ $t('clickToShow') }}</span> | 		<div> | ||||||
|  | 			<b><fa :icon="faExclamationTriangle"/> {{ $t('sensitive') }}</b> | ||||||
|  | 			<span>{{ $t('clickToShow') }}</span> | ||||||
|  | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| <div class="gqnyydlzavusgskkfvwvjiattxdzsqlf" v-else> | <div class="gqnyydlz" v-else> | ||||||
| 	<i><fa :icon="faEyeSlash" @click="hide = true"/></i> | 	<i><fa :icon="faEyeSlash" @click="hide = true"/></i> | ||||||
| 	<a | 	<a | ||||||
| 		:href="image.url" | 		:href="image.url" | ||||||
| 		:style="style" |  | ||||||
| 		:title="image.name" | 		:title="image.name" | ||||||
| 		@click.prevent="onClick" | 		@click.prevent="onClick" | ||||||
| 	> | 	> | ||||||
| 		<div v-if="image.type === 'image/gif'">GIF</div> | 		<img-with-blurhash :hash="image.blurhash" :src="url" :alt="image.name" :title="image.name"/> | ||||||
|  | 		<div class="gif" v-if="image.type === 'image/gif'">GIF</div> | ||||||
| 	</a> | 	</a> | ||||||
| </div> | </div> | ||||||
| </template> | </template> | ||||||
|  | @ -23,8 +26,12 @@ import Vue from 'vue'; | ||||||
| import { faExclamationTriangle, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; | import { faExclamationTriangle, faEyeSlash } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { getStaticImageUrl } from '../scripts/get-static-image-url'; | import { getStaticImageUrl } from '../scripts/get-static-image-url'; | ||||||
| import ImageViewer from './image-viewer.vue'; | import ImageViewer from './image-viewer.vue'; | ||||||
|  | import ImgWithBlurhash from './img-with-blurhash.vue'; | ||||||
| 
 | 
 | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
|  | 	components: { | ||||||
|  | 		ImgWithBlurhash | ||||||
|  | 	}, | ||||||
| 	props: { | 	props: { | ||||||
| 		image: { | 		image: { | ||||||
| 			type: Object, | 			type: Object, | ||||||
|  | @ -42,23 +49,18 @@ export default Vue.extend({ | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| 	computed: { | 	computed: { | ||||||
| 		style(): any { | 		url(): any { | ||||||
| 			let url = `url(${ | 			let url = this.$store.state.device.disableShowingAnimatedImages | ||||||
| 				this.$store.state.device.disableShowingAnimatedImages | 				? getStaticImageUrl(this.image.thumbnailUrl) | ||||||
| 					? getStaticImageUrl(this.image.thumbnailUrl) | 				: this.image.thumbnailUrl; | ||||||
| 					: this.image.thumbnailUrl |  | ||||||
| 			})`; |  | ||||||
| 
 | 
 | ||||||
| 			if (this.$store.state.device.loadRemoteMedia) { | 			if (this.$store.state.device.loadRemoteMedia) { | ||||||
| 				url = null; | 				url = null; | ||||||
| 			} else if (this.raw || this.$store.state.device.loadRawImages) { | 			} else if (this.raw || this.$store.state.device.loadRawImages) { | ||||||
| 				url = `url(${this.image.url})`; | 				url = this.image.url; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			return { | 			return url; | ||||||
| 				'background-color': this.image.properties.avgColor || 'transparent', |  | ||||||
| 				'background-image': url |  | ||||||
| 			}; |  | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	created() { | 	created() { | ||||||
|  | @ -82,7 +84,38 @@ export default Vue.extend({ | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| .gqnyydlzavusgskkfvwvjiattxdzsqlf { | .qjewsnkg { | ||||||
|  | 	position: relative; | ||||||
|  | 
 | ||||||
|  | 	> .bg { | ||||||
|  | 		filter: brightness(0.5); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	> .text { | ||||||
|  | 		position: absolute; | ||||||
|  | 		left: 0; | ||||||
|  | 		top: 0; | ||||||
|  | 		width: 100%; | ||||||
|  | 		height: 100%; | ||||||
|  | 		z-index: 1; | ||||||
|  | 		display: flex; | ||||||
|  | 		justify-content: center; | ||||||
|  | 		align-items: center; | ||||||
|  | 
 | ||||||
|  | 		> div { | ||||||
|  | 			display: table-cell; | ||||||
|  | 			text-align: center; | ||||||
|  | 			font-size: 0.8em; | ||||||
|  | 			color: #fff; | ||||||
|  | 
 | ||||||
|  | 			> * { | ||||||
|  | 				display: block; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .gqnyydlz { | ||||||
| 	position: relative; | 	position: relative; | ||||||
| 
 | 
 | ||||||
| 	> i { | 	> i { | ||||||
|  | @ -110,7 +143,7 @@ export default Vue.extend({ | ||||||
| 		background-size: contain; | 		background-size: contain; | ||||||
| 		background-repeat: no-repeat; | 		background-repeat: no-repeat; | ||||||
| 
 | 
 | ||||||
| 		> div { | 		> .gif { | ||||||
| 			background-color: var(--fg); | 			background-color: var(--fg); | ||||||
| 			border-radius: 6px; | 			border-radius: 6px; | ||||||
| 			color: var(--accentLighten); | 			color: var(--accentLighten); | ||||||
|  | @ -126,22 +159,4 @@ export default Vue.extend({ | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 |  | ||||||
| .qjewsnkgzzxlxtzncydssfbgjibiehcy { |  | ||||||
| 	display: flex; |  | ||||||
| 	justify-content: center; |  | ||||||
| 	align-items: center; |  | ||||||
| 	background: #111; |  | ||||||
| 	color: #fff; |  | ||||||
| 
 |  | ||||||
| 	> div { |  | ||||||
| 		display: table-cell; |  | ||||||
| 		text-align: center; |  | ||||||
| 		font-size: 12px; |  | ||||||
| 
 |  | ||||||
| 		> * { |  | ||||||
| 			display: block; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -114,7 +114,7 @@ export default Vue.extend({ | ||||||
| 
 | 
 | ||||||
| 			> * { | 			> * { | ||||||
| 				overflow: hidden; | 				overflow: hidden; | ||||||
| 				border-radius: 4px; | 				border-radius: 6px; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			&[data-count="1"] { | 			&[data-count="1"] { | ||||||
|  |  | ||||||
|  | @ -10,8 +10,7 @@ | ||||||
| 				<mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/> | 				<mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/> | ||||||
| 				<div class="file" v-if="message.file"> | 				<div class="file" v-if="message.file"> | ||||||
| 					<a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name"> | 					<a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name"> | ||||||
| 						<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name" | 						<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"/> | ||||||
| 							:style="{ backgroundColor: message.file.properties.avgColor || 'transparent' }"/> |  | ||||||
| 						<p v-else>{{ message.file.name }}</p> | 						<p v-else>{{ message.file.name }}</p> | ||||||
| 					</a> | 					</a> | ||||||
| 				</div> | 				</div> | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
| 	</template> | 	</template> | ||||||
| 
 | 
 | ||||||
| 	<section class="oyyftmcf"> | 	<section class="oyyftmcf"> | ||||||
| 		<mk-file-thumbnail class="preview" v-if="file" :file="file" :detail="true" fit="contain" @click="choose()"/> | 		<mk-file-thumbnail class="preview" v-if="file" :file="file" fit="contain" @click="choose()"/> | ||||||
| 	</section> | 	</section> | ||||||
| </x-container> | </x-container> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -123,10 +123,6 @@ a { | ||||||
| 	&:hover { | 	&:hover { | ||||||
| 		text-decoration: underline; | 		text-decoration: underline; | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	* { |  | ||||||
| 		cursor: pointer; |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| hr { | hr { | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import * as fileType from 'file-type'; | ||||||
| import isSvg from 'is-svg'; | import isSvg from 'is-svg'; | ||||||
| import * as probeImageSize from 'probe-image-size'; | import * as probeImageSize from 'probe-image-size'; | ||||||
| import * as sharp from 'sharp'; | import * as sharp from 'sharp'; | ||||||
|  | import { encode } from 'blurhash'; | ||||||
| 
 | 
 | ||||||
| const pipeline = util.promisify(stream.pipeline); | const pipeline = util.promisify(stream.pipeline); | ||||||
| 
 | 
 | ||||||
|  | @ -18,7 +19,7 @@ export type FileInfo = { | ||||||
| 	}; | 	}; | ||||||
| 	width?: number; | 	width?: number; | ||||||
| 	height?: number; | 	height?: number; | ||||||
| 	avgColor?: number[]; | 	blurhash?: string; | ||||||
| 	warnings: string[]; | 	warnings: string[]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -71,12 +72,11 @@ export async function getFileInfo(path: string): Promise<FileInfo> { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// average color
 | 	let blurhash: string | undefined; | ||||||
| 	let avgColor: number[] | undefined; |  | ||||||
| 
 | 
 | ||||||
| 	if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) { | 	if (['image/jpeg', 'image/gif', 'image/png', 'image/apng', 'image/webp', 'image/svg+xml'].includes(type.mime)) { | ||||||
| 		avgColor = await calcAvgColor(path).catch(e => { | 		blurhash = await getBlurhash(path).catch(e => { | ||||||
| 			warnings.push(`calcAvgColor failed: ${e}`); | 			warnings.push(`getBlurhash failed: ${e}`); | ||||||
| 			return undefined; | 			return undefined; | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  | @ -87,7 +87,7 @@ export async function getFileInfo(path: string): Promise<FileInfo> { | ||||||
| 		type, | 		type, | ||||||
| 		width, | 		width, | ||||||
| 		height, | 		height, | ||||||
| 		avgColor, | 		blurhash, | ||||||
| 		warnings, | 		warnings, | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
|  | @ -173,18 +173,15 @@ async function detectImageSize(path: string): Promise<{ | ||||||
| /** | /** | ||||||
|  * Calculate average color of image |  * Calculate average color of image | ||||||
|  */ |  */ | ||||||
| async function calcAvgColor(path: string): Promise<number[]> { | function getBlurhash(path: string): Promise<string> { | ||||||
| 	const img = sharp(path); | 	return new Promise((resolve, reject) => { | ||||||
| 
 | 		sharp(path) | ||||||
| 	const info = await (img as any).stats(); | 			.raw() | ||||||
| 
 | 			.ensureAlpha() | ||||||
| 	if (info.isOpaque) { | 			.resize(64, 64, { fit: 'inside' }) | ||||||
| 		const r = Math.round(info.channels[0].mean); | 			.toBuffer((err, buffer, { width, height }) => { | ||||||
| 		const g = Math.round(info.channels[1].mean); | 				if (err) return reject(err); | ||||||
| 		const b = Math.round(info.channels[2].mean); | 				resolve(encode(new Uint8ClampedArray(buffer), width, height, 7, 7)); | ||||||
| 
 | 			}); | ||||||
| 		return [r, g, b]; | 	}); | ||||||
| 	} else { |  | ||||||
| 		return [255, 255, 255]; |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -67,6 +67,12 @@ export class DriveFile { | ||||||
| 	}) | 	}) | ||||||
| 	public comment: string | null; | 	public comment: string | null; | ||||||
| 
 | 
 | ||||||
|  | 	@Column('varchar', { | ||||||
|  | 		length: 128, nullable: true, | ||||||
|  | 		comment: 'The BlurHash string.' | ||||||
|  | 	}) | ||||||
|  | 	public blurhash: string | null; | ||||||
|  | 
 | ||||||
| 	@Column('jsonb', { | 	@Column('jsonb', { | ||||||
| 		default: {}, | 		default: {}, | ||||||
| 		comment: 'The any properties of the DriveFile. For example, it includes image width/height.' | 		comment: 'The any properties of the DriveFile. For example, it includes image width/height.' | ||||||
|  |  | ||||||
|  | @ -106,14 +106,14 @@ export class User { | ||||||
| 	public bannerUrl: string | null; | 	public bannerUrl: string | null; | ||||||
| 
 | 
 | ||||||
| 	@Column('varchar', { | 	@Column('varchar', { | ||||||
| 		length: 32, nullable: true, | 		length: 128, nullable: true, | ||||||
| 	}) | 	}) | ||||||
| 	public avatarColor: string | null; | 	public avatarBlurhash: string | null; | ||||||
| 
 | 
 | ||||||
| 	@Column('varchar', { | 	@Column('varchar', { | ||||||
| 		length: 32, nullable: true, | 		length: 128, nullable: true, | ||||||
| 	}) | 	}) | ||||||
| 	public bannerColor: string | null; | 	public bannerBlurhash: string | null; | ||||||
| 
 | 
 | ||||||
| 	@Column('boolean', { | 	@Column('boolean', { | ||||||
| 		default: false, | 		default: false, | ||||||
|  |  | ||||||
|  | @ -115,6 +115,7 @@ export class DriveFileRepository extends Repository<DriveFile> { | ||||||
| 			md5: file.md5, | 			md5: file.md5, | ||||||
| 			size: file.size, | 			size: file.size, | ||||||
| 			isSensitive: file.isSensitive, | 			isSensitive: file.isSensitive, | ||||||
|  | 			blurhash: file.blurhash, | ||||||
| 			properties: file.properties, | 			properties: file.properties, | ||||||
| 			url: opts.self ? file.url : this.getPublicUrl(file, false, meta), | 			url: opts.self ? file.url : this.getPublicUrl(file, false, meta), | ||||||
| 			thumbnailUrl: this.getPublicUrl(file, true, meta), | 			thumbnailUrl: this.getPublicUrl(file, true, meta), | ||||||
|  |  | ||||||
|  | @ -165,7 +165,8 @@ export class UserRepository extends Repository<User> { | ||||||
| 			username: user.username, | 			username: user.username, | ||||||
| 			host: user.host, | 			host: user.host, | ||||||
| 			avatarUrl: user.avatarUrl ? user.avatarUrl : config.url + '/avatar/' + user.id, | 			avatarUrl: user.avatarUrl ? user.avatarUrl : config.url + '/avatar/' + user.id, | ||||||
| 			avatarColor: user.avatarColor, | 			avatarBlurhash: user.avatarBlurhash, | ||||||
|  | 			avatarColor: null, // 後方互換性のため
 | ||||||
| 			isAdmin: user.isAdmin || falsy, | 			isAdmin: user.isAdmin || falsy, | ||||||
| 			isModerator: user.isModerator || falsy, | 			isModerator: user.isModerator || falsy, | ||||||
| 			isBot: user.isBot || falsy, | 			isBot: user.isBot || falsy, | ||||||
|  | @ -196,7 +197,8 @@ export class UserRepository extends Repository<User> { | ||||||
| 				createdAt: user.createdAt.toISOString(), | 				createdAt: user.createdAt.toISOString(), | ||||||
| 				updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, | 				updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, | ||||||
| 				bannerUrl: user.bannerUrl, | 				bannerUrl: user.bannerUrl, | ||||||
| 				bannerColor: user.bannerColor, | 				bannerBlurhash: user.bannerBlurhash, | ||||||
|  | 				bannerColor: null, // 後方互換性のため
 | ||||||
| 				isLocked: user.isLocked, | 				isLocked: user.isLocked, | ||||||
| 				isModerator: user.isModerator || falsy, | 				isModerator: user.isModerator || falsy, | ||||||
| 				isSilenced: user.isSilenced || falsy, | 				isSilenced: user.isSilenced || falsy, | ||||||
|  | @ -331,7 +333,7 @@ export const packedUserSchema = { | ||||||
| 			format: 'url', | 			format: 'url', | ||||||
| 			nullable: true as const, optional: false as const, | 			nullable: true as const, optional: false as const, | ||||||
| 		}, | 		}, | ||||||
| 		avatarColor: { | 		avatarBlurhash: { | ||||||
| 			type: 'any' as const, | 			type: 'any' as const, | ||||||
| 			nullable: true as const, optional: false as const, | 			nullable: true as const, optional: false as const, | ||||||
| 		}, | 		}, | ||||||
|  | @ -340,7 +342,7 @@ export const packedUserSchema = { | ||||||
| 			format: 'url', | 			format: 'url', | ||||||
| 			nullable: true as const, optional: true as const, | 			nullable: true as const, optional: true as const, | ||||||
| 		}, | 		}, | ||||||
| 		bannerColor: { | 		bannerBlurhash: { | ||||||
| 			type: 'any' as const, | 			type: 'any' as const, | ||||||
| 			nullable: true as const, optional: true as const, | 			nullable: true as const, optional: true as const, | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -226,24 +226,24 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us | ||||||
| 	const bannerId = banner ? banner.id : null; | 	const bannerId = banner ? banner.id : null; | ||||||
| 	const avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar, true) : null; | 	const avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar, true) : null; | ||||||
| 	const bannerUrl = banner ? DriveFiles.getPublicUrl(banner) : null; | 	const bannerUrl = banner ? DriveFiles.getPublicUrl(banner) : null; | ||||||
| 	const avatarColor = avatar && avatar.properties.avgColor ? avatar.properties.avgColor : null; | 	const avatarBlurhash = avatar ? avatar.blurhash : null; | ||||||
| 	const bannerColor = banner && banner.properties.avgColor ? banner.properties.avgColor : null; | 	const bannerBlurhash = banner ? banner.blurhash : null; | ||||||
| 
 | 
 | ||||||
| 	await Users.update(user!.id, { | 	await Users.update(user!.id, { | ||||||
| 		avatarId, | 		avatarId, | ||||||
| 		bannerId, | 		bannerId, | ||||||
| 		avatarUrl, | 		avatarUrl, | ||||||
| 		bannerUrl, | 		bannerUrl, | ||||||
| 		avatarColor, | 		avatarBlurhash, | ||||||
| 		bannerColor | 		bannerBlurhash | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	user!.avatarId = avatarId; | 	user!.avatarId = avatarId; | ||||||
| 	user!.bannerId = bannerId; | 	user!.bannerId = bannerId; | ||||||
| 	user!.avatarUrl = avatarUrl; | 	user!.avatarUrl = avatarUrl; | ||||||
| 	user!.bannerUrl = bannerUrl; | 	user!.bannerUrl = bannerUrl; | ||||||
| 	user!.avatarColor = avatarColor; | 	user!.avatarBlurhash = avatarBlurhash; | ||||||
| 	user!.bannerColor = bannerColor; | 	user!.bannerBlurhash = bannerBlurhash; | ||||||
| 	//#endregion
 | 	//#endregion
 | ||||||
| 
 | 
 | ||||||
| 	//#region カスタム絵文字取得
 | 	//#region カスタム絵文字取得
 | ||||||
|  | @ -341,13 +341,13 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint | ||||||
| 	if (avatar) { | 	if (avatar) { | ||||||
| 		updates.avatarId = avatar.id; | 		updates.avatarId = avatar.id; | ||||||
| 		updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true); | 		updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true); | ||||||
| 		updates.avatarColor = avatar.properties.avgColor ? avatar.properties.avgColor : null; | 		updates.avatarBlurhash = avatar.blurhash; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (banner) { | 	if (banner) { | ||||||
| 		updates.bannerId = banner.id; | 		updates.bannerId = banner.id; | ||||||
| 		updates.bannerUrl = DriveFiles.getPublicUrl(banner); | 		updates.bannerUrl = DriveFiles.getPublicUrl(banner); | ||||||
| 		updates.bannerColor = banner.properties.avgColor ? banner.properties.avgColor : null; | 		updates.bannerBlurhash = banner.blurhash; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Update user
 | 	// Update user
 | ||||||
|  |  | ||||||
|  | @ -210,8 +210,8 @@ export default define(meta, async (ps, user, token) => { | ||||||
| 
 | 
 | ||||||
| 		updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true); | 		updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true); | ||||||
| 
 | 
 | ||||||
| 		if (avatar.properties.avgColor) { | 		if (avatar.blurhash) { | ||||||
| 			updates.avatarColor = avatar.properties.avgColor; | 			updates.avatarBlurhash = avatar.blurhash; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -223,8 +223,8 @@ export default define(meta, async (ps, user, token) => { | ||||||
| 
 | 
 | ||||||
| 		updates.bannerUrl = DriveFiles.getPublicUrl(banner, false); | 		updates.bannerUrl = DriveFiles.getPublicUrl(banner, false); | ||||||
| 
 | 
 | ||||||
| 		if (banner.properties.avgColor) { | 		if (banner.blurhash) { | ||||||
| 			updates.bannerColor = banner.properties.avgColor; | 			updates.bannerBlurhash = banner.blurhash; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -327,7 +327,6 @@ export default async function( | ||||||
| 	const properties: { | 	const properties: { | ||||||
| 		width?: number; | 		width?: number; | ||||||
| 		height?: number; | 		height?: number; | ||||||
| 		avgColor?: string; |  | ||||||
| 	} = {}; | 	} = {}; | ||||||
| 
 | 
 | ||||||
| 	if (info.width) { | 	if (info.width) { | ||||||
|  | @ -335,10 +334,6 @@ export default async function( | ||||||
| 		properties['height'] = info.height; | 		properties['height'] = info.height; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (info.avgColor) { |  | ||||||
| 		properties['avgColor'] = `rgb(${info.avgColor.join(',')})`; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	const profile = user ? await UserProfiles.findOne(user.id) : null; | 	const profile = user ? await UserProfiles.findOne(user.id) : null; | ||||||
| 
 | 
 | ||||||
| 	const folder = await fetchFolder(); | 	const folder = await fetchFolder(); | ||||||
|  | @ -351,6 +346,7 @@ export default async function( | ||||||
| 	file.folderId = folder !== null ? folder.id : null; | 	file.folderId = folder !== null ? folder.id : null; | ||||||
| 	file.comment = comment; | 	file.comment = comment; | ||||||
| 	file.properties = properties; | 	file.properties = properties; | ||||||
|  | 	file.blurhash = info.blurhash || null; | ||||||
| 	file.isLink = isLink; | 	file.isLink = isLink; | ||||||
| 	file.isSensitive = user | 	file.isSensitive = user | ||||||
| 		? Users.isLocalUser(user) && profile!.alwaysMarkNsfw ? true : | 		? Users.isLocalUser(user) && profile!.alwaysMarkNsfw ? true : | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ describe('Get file info', () => { | ||||||
| 			}, | 			}, | ||||||
| 			width: undefined, | 			width: undefined, | ||||||
| 			height: undefined, | 			height: undefined, | ||||||
| 			avgColor: undefined | 			blurhash: null | ||||||
| 		}); | 		}); | ||||||
| 	})); | 	})); | ||||||
| 
 | 
 | ||||||
|  | @ -43,7 +43,7 @@ describe('Get file info', () => { | ||||||
| 			}, | 			}, | ||||||
| 			width: 512, | 			width: 512, | ||||||
| 			height: 512, | 			height: 512, | ||||||
| 			avgColor: [ 181, 99, 106 ] | 			blurhash: '' // TODO
 | ||||||
| 		}); | 		}); | ||||||
| 	})); | 	})); | ||||||
| 
 | 
 | ||||||
|  | @ -60,7 +60,7 @@ describe('Get file info', () => { | ||||||
| 			}, | 			}, | ||||||
| 			width: 256, | 			width: 256, | ||||||
| 			height: 256, | 			height: 256, | ||||||
| 			avgColor: [ 249, 253, 250 ] | 			blurhash: '' // TODO
 | ||||||
| 		}); | 		}); | ||||||
| 	})); | 	})); | ||||||
| 
 | 
 | ||||||
|  | @ -77,7 +77,7 @@ describe('Get file info', () => { | ||||||
| 			}, | 			}, | ||||||
| 			width: 256, | 			width: 256, | ||||||
| 			height: 256, | 			height: 256, | ||||||
| 			avgColor: [ 249, 253, 250 ] | 			blurhash: '' // TODO
 | ||||||
| 		}); | 		}); | ||||||
| 	})); | 	})); | ||||||
| 
 | 
 | ||||||
|  | @ -94,7 +94,7 @@ describe('Get file info', () => { | ||||||
| 			}, | 			}, | ||||||
| 			width: 256, | 			width: 256, | ||||||
| 			height: 256, | 			height: 256, | ||||||
| 			avgColor: [ 255, 255, 255 ] | 			blurhash: '' // TODO
 | ||||||
| 		}); | 		}); | ||||||
| 	})); | 	})); | ||||||
| 
 | 
 | ||||||
|  | @ -111,7 +111,7 @@ describe('Get file info', () => { | ||||||
| 			}, | 			}, | ||||||
| 			width: 256, | 			width: 256, | ||||||
| 			height: 256, | 			height: 256, | ||||||
| 			avgColor: [ 255, 255, 255 ] | 			blurhash: '' // TODO
 | ||||||
| 		}); | 		}); | ||||||
| 	})); | 	})); | ||||||
| 
 | 
 | ||||||
|  | @ -129,7 +129,7 @@ describe('Get file info', () => { | ||||||
| 			}, | 			}, | ||||||
| 			width: 256, | 			width: 256, | ||||||
| 			height: 256, | 			height: 256, | ||||||
| 			avgColor: [ 255, 255, 255 ] | 			blurhash: '' // TODO
 | ||||||
| 		}); | 		}); | ||||||
| 	})); | 	})); | ||||||
| 
 | 
 | ||||||
|  | @ -146,7 +146,7 @@ describe('Get file info', () => { | ||||||
| 			}, | 			}, | ||||||
| 			width: 25000, | 			width: 25000, | ||||||
| 			height: 25000, | 			height: 25000, | ||||||
| 			avgColor: undefined | 			blurhash: '' // TODO
 | ||||||
| 		}); | 		}); | ||||||
| 	})); | 	})); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -1669,6 +1669,11 @@ bluebird@^3.1.1, bluebird@^3.4.1: | ||||||
|   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" |   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" | ||||||
|   integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== |   integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== | ||||||
| 
 | 
 | ||||||
|  | blurhash@1.1.3: | ||||||
|  |   version "1.1.3" | ||||||
|  |   resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.3.tgz#dc325af7da836d07a0861d830bdd63694382483e" | ||||||
|  |   integrity sha512-yUhPJvXexbqbyijCIE/T2NCXcj9iNPhWmOKbPTuR/cm7Q5snXYIfnVnz6m7MWOXxODMz/Cr3UcVkRdHiuDVRDw== | ||||||
|  | 
 | ||||||
| bn.js@^4.0.0: | bn.js@^4.0.0: | ||||||
|   version "4.11.8" |   version "4.11.8" | ||||||
|   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" |   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue