watermark wip
This commit is contained in:
		
							parent
							
								
									e8bf6285cb
								
							
						
					
					
						commit
						a0fc59bd4b
					
				|  | @ -1902,6 +1902,18 @@ export interface Locale extends ILocale { | |||
|      * 既定アップロード先 | ||||
|      */ | ||||
|     "uploadFolder": string; | ||||
|     /** | ||||
|      * ウォーターマーク | ||||
|      */ | ||||
|     "watermark": string; | ||||
|     /** | ||||
|      * ウォーターマークをつける | ||||
|      */ | ||||
|     "useWatermark": string; | ||||
|     /** | ||||
|      * 画像にウォーターマークを追加します | ||||
|      */ | ||||
|     "useWatermarkDescription": string; | ||||
|     /** | ||||
|      * すべての通知を既読にする | ||||
|      */ | ||||
|  | @ -3143,13 +3155,25 @@ export interface Locale extends ILocale { | |||
|      */ | ||||
|     "duplicate": string; | ||||
|     /** | ||||
|      * 左 | ||||
|      * 上 | ||||
|      */ | ||||
|     "left": string; | ||||
|     "top": string; | ||||
|     /** | ||||
|      * 下 | ||||
|      */ | ||||
|     "bottom": string; | ||||
|     /** | ||||
|      * 中央 | ||||
|      */ | ||||
|     "center": string; | ||||
|     /** | ||||
|      * 左 | ||||
|      */ | ||||
|     "left": string; | ||||
|     /** | ||||
|      * 右 | ||||
|      */ | ||||
|     "right": string; | ||||
|     /** | ||||
|      * 広い | ||||
|      */ | ||||
|  | @ -4458,18 +4482,38 @@ export interface Locale extends ILocale { | |||
|      * 通知の表示 | ||||
|      */ | ||||
|     "notificationDisplay": string; | ||||
|     /** | ||||
|      * 配置 | ||||
|      */ | ||||
|     "placement": string; | ||||
|     /** | ||||
|      * 左上 | ||||
|      */ | ||||
|     "leftTop": string; | ||||
|     /** | ||||
|      * 中上 | ||||
|      */ | ||||
|     "centerTop": string; | ||||
|     /** | ||||
|      * 右上 | ||||
|      */ | ||||
|     "rightTop": string; | ||||
|     /** | ||||
|      * 左中 | ||||
|      */ | ||||
|     "leftCenter": string; | ||||
|     /** | ||||
|      * 右中 | ||||
|      */ | ||||
|     "rightCenter": string; | ||||
|     /** | ||||
|      * 左下 | ||||
|      */ | ||||
|     "leftBottom": string; | ||||
|     /** | ||||
|      * 中下 | ||||
|      */ | ||||
|     "centerBottom": string; | ||||
|     /** | ||||
|      * 右下 | ||||
|      */ | ||||
|  | @ -4490,6 +4534,22 @@ export interface Locale extends ILocale { | |||
|      * 位置 | ||||
|      */ | ||||
|     "position": string; | ||||
|     /** | ||||
|      * 繰り返し | ||||
|      */ | ||||
|     "repeat": string; | ||||
|     /** | ||||
|      * 引き伸ばし | ||||
|      */ | ||||
|     "enlargement": string; | ||||
|     /** | ||||
|      * 回転 | ||||
|      */ | ||||
|     "rotate": string; | ||||
|     /** | ||||
|      * 透明度 | ||||
|      */ | ||||
|     "opacity": string; | ||||
|     /** | ||||
|      * サーバールール | ||||
|      */ | ||||
|  |  | |||
|  | @ -471,6 +471,9 @@ share: "共有" | |||
| notFound: "見つかりません" | ||||
| notFoundDescription: "指定されたURLに該当するページはありませんでした。" | ||||
| uploadFolder: "既定アップロード先" | ||||
| watermark: "ウォーターマーク" | ||||
| useWatermark: "ウォーターマークをつける" | ||||
| useWatermarkDescription: "画像にウォーターマークを追加します" | ||||
| markAsReadAllNotifications: "すべての通知を既読にする" | ||||
| markAsReadAllUnreadNotes: "すべての投稿を既読にする" | ||||
| markAsReadAllTalkMessages: "すべてのチャットを既読にする" | ||||
|  | @ -781,8 +784,11 @@ makeExplorable: "アカウントを見つけやすくする" | |||
| makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らなくなります。" | ||||
| showGapBetweenNotesInTimeline: "タイムラインのノートを離して表示" | ||||
| duplicate: "複製" | ||||
| left: "左" | ||||
| top: "上" | ||||
| bottom: "下" | ||||
| center: "中央" | ||||
| left: "左" | ||||
| right: "右" | ||||
| wide: "広い" | ||||
| narrow: "狭い" | ||||
| reloadToApplySetting: "設定はページリロード後に反映されます。" | ||||
|  | @ -1110,14 +1116,23 @@ editMemo: "メモを編集" | |||
| reactionsList: "リアクション一覧" | ||||
| renotesList: "リノート一覧" | ||||
| notificationDisplay: "通知の表示" | ||||
| placement: "配置" | ||||
| leftTop: "左上" | ||||
| centerTop: "中上" | ||||
| rightTop: "右上" | ||||
| leftCenter: "左中" | ||||
| rightCenter: "右中" | ||||
| leftBottom: "左下" | ||||
| centerBottom: "中下" | ||||
| rightBottom: "右下" | ||||
| stackAxis: "スタック方向" | ||||
| vertical: "縦" | ||||
| horizontal: "横" | ||||
| position: "位置" | ||||
| repeat: "繰り返し" | ||||
| enlargement: "引き伸ばし" | ||||
| rotate: "回転" | ||||
| opacity: "透明度" | ||||
| serverRules: "サーバールール" | ||||
| pleaseConfirmBelowBeforeSignup: "このサーバーに登録するには、以下の内容を確認し同意する必要があります。" | ||||
| pleaseAgreeAllToContinue: "続けるには、全ての「同意する」にチェックが入っている必要があります。" | ||||
|  |  | |||
|  | @ -86,6 +86,7 @@ export const ROLE_POLICIES = [ | |||
| 	'canManageCustomEmojis', | ||||
| 	'canManageAvatarDecorations', | ||||
| 	'canSearchNotes', | ||||
| 	'canUseReaction', | ||||
| 	'canUseTranslator', | ||||
| 	'canHideAds', | ||||
| 	'driveCapacityMb', | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.8 KiB | 
|  | @ -106,6 +106,7 @@ import XFile from '@/components/MkDrive.file.vue'; | |||
| import * as os from '@/os.js'; | ||||
| import { misskeyApi } from '@/scripts/misskey-api.js'; | ||||
| import { useStream } from '@/stream.js'; | ||||
| import { $i } from '@/account.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| import { uploadFile, uploads } from '@/scripts/upload.js'; | ||||
|  | @ -143,6 +144,7 @@ const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]); | |||
| const uploadings = uploads; | ||||
| const connection = useStream().useChannel('drive'); | ||||
| const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい | ||||
| const useWatermark = ref<boolean>(defaultStore.state.useWatermark); | ||||
| 
 | ||||
| // ドロップされようとしているか | ||||
| const draghover = ref(false); | ||||
|  | @ -391,7 +393,7 @@ function onChangeFileInput() { | |||
| } | ||||
| 
 | ||||
| function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) { | ||||
| 	uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal.value).then(res => { | ||||
| 	uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal.value, useWatermark.value).then(res => { | ||||
| 		addFile(res, true); | ||||
| 	}); | ||||
| } | ||||
|  | @ -633,7 +635,12 @@ function getMenu() { | |||
| 		type: 'switch', | ||||
| 		text: i18n.ts.keepOriginalUploading, | ||||
| 		ref: keepOriginal, | ||||
| 	}, { type: 'divider' }, { | ||||
| 	}, ...($i?.policies.canUseWatermark ? [{ | ||||
| 			type: 'switch', | ||||
| 			text: i18n.ts.useWatermark, | ||||
| 			ref: useWatermark, | ||||
| 		}] as MenuItem[] : [] | ||||
| 	), { type: 'divider' }, { | ||||
| 		text: i18n.ts.addFile, | ||||
| 		type: 'label', | ||||
| 	}, { | ||||
|  |  | |||
|  | @ -89,14 +89,6 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 							</MkInput> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder v-if="matchQuery([i18n.ts._role._options.canManageAvatarDecorations, 'canManageAvatarDecorations'])"> | ||||
| 							<template #label>{{ i18n.ts._role._options.canManageAvatarDecorations }}</template> | ||||
| 							<template #suffix>{{ policies.canManageAvatarDecorations ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="policies.canManageAvatarDecorations"> | ||||
| 								<template #label>{{ i18n.ts.enable }}</template> | ||||
| 							</MkSwitch> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder v-if="matchQuery([i18n.ts._role._options.canManageCustomEmojis, 'canManageCustomEmojis'])"> | ||||
| 							<template #label>{{ i18n.ts._role._options.canManageCustomEmojis }}</template> | ||||
| 							<template #suffix>{{ policies.canManageCustomEmojis ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
|  | @ -105,6 +97,14 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 							</MkSwitch> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder v-if="matchQuery([i18n.ts._role._options.canManageAvatarDecorations, 'canManageAvatarDecorations'])"> | ||||
| 							<template #label>{{ i18n.ts._role._options.canManageAvatarDecorations }}</template> | ||||
| 							<template #suffix>{{ policies.canManageAvatarDecorations ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
| 							<MkSwitch v-model="policies.canManageAvatarDecorations"> | ||||
| 								<template #label>{{ i18n.ts.enable }}</template> | ||||
| 							</MkSwitch> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 						<MkFolder v-if="matchQuery([i18n.ts._role._options.canSearchNotes, 'canSearchNotes'])"> | ||||
| 							<template #label>{{ i18n.ts._role._options.canSearchNotes }}</template> | ||||
| 							<template #suffix>{{ policies.canSearchNotes ? i18n.ts.yes : i18n.ts.no }}</template> | ||||
|  |  | |||
|  | @ -33,11 +33,36 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 	<FormSection> | ||||
| 		<div class="_gaps_m"> | ||||
| 			<FormLink @click="chooseUploadFolder()"> | ||||
| 				<template #icon><i class="ti ti-folder"/></template> | ||||
| 				{{ i18n.ts.uploadFolder }} | ||||
| 				<template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template> | ||||
| 				<template #suffixIcon><i class="ti ti-folder"></i></template> | ||||
| 			</FormLink> | ||||
| 			<MkFolder v-if="$i?.policies.canUseWatermark"> | ||||
| 				<template #icon><i class="ti ti-ripple"></i></template> | ||||
| 				<template #label>{{ i18n.ts.watermark }}</template> | ||||
| 				<div class="_gaps_s"> | ||||
| 					<MkSwitch v-model="useWatermark"> | ||||
| 						<template #label>{{ i18n.ts.useWatermark }}</template> | ||||
| 						<template #caption>{{ i18n.ts.useWatermarkDescription }}</template> | ||||
| 					</MkSwitch> | ||||
| 					<FormSection> | ||||
| 						<template #label>{{ i18n.ts.preview }}</template> | ||||
| 						<div style="display: flex; justify-content: center; align-items: center;"> | ||||
| 							<div style="width: 80%; height: 400px; border: 1px solid #ccc; background-image: url('/client-assets/tutorial/natto_failed.webp'); background-size: cover;"> | ||||
| 								<img src="/client-assets/default-watermark.png" style="width: 100%; height: 100%; object-fit: contain; opacity: 0.5;"/> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 						<FormSplit> | ||||
| 							<div class="_buttons"> | ||||
| 								<MkButton primary rounded><i class="ti ti-photo"/>{{ i18n.ts.selectFile }}</MkButton> | ||||
| 								<MkButton danger rounded><i class="ti ti-trash"></i> {{ i18n.ts.defa }}</MkButton> | ||||
| 							</div> | ||||
| 						</FormSplit> | ||||
| 					</FormSection> | ||||
| 				</div> | ||||
| 			</MkFolder> | ||||
| 			<FormLink to="/settings/drive/cleaner"> | ||||
| 				<template #icon><i class="ti ti-file-shredder"/></template> | ||||
| 				{{ i18n.ts.drivecleaner }} | ||||
| 			</FormLink> | ||||
| 			<MkSwitch v-model="keepOriginalUploading"> | ||||
|  | @ -77,6 +102,9 @@ import MkChart from '@/components/MkChart.vue'; | |||
| import { i18n } from '@/i18n.js'; | ||||
| import { definePageMetadata } from '@/scripts/page-metadata.js'; | ||||
| import { signinRequired } from '@/account.js'; | ||||
| import MkInfo from "@/components/MkInfo.vue"; | ||||
| import MkButton from "@/components/MkButton.vue"; | ||||
| import MkFolder from "@/components/MkFolder.vue"; | ||||
| 
 | ||||
| const $i = signinRequired(); | ||||
| 
 | ||||
|  | @ -102,6 +130,9 @@ const meterStyle = computed(() => { | |||
| const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading')); | ||||
| const keepOriginalFilename = computed(defaultStore.makeGetterSetter('keepOriginalFilename')); | ||||
| 
 | ||||
| const useWatermark = computed(defaultStore.makeGetterSetter('useWatermark')); | ||||
| const watermarkConfig = computed(defaultStore.makeGetterSetter('watermarkConfig')); | ||||
| 
 | ||||
| misskeyApi('drive').then(info => { | ||||
| 	capacity.value = info.capacity; | ||||
| 	usage.value = info.usage; | ||||
|  |  | |||
|  | @ -9,17 +9,19 @@ import * as os from '@/os.js'; | |||
| import { misskeyApi } from '@/scripts/misskey-api.js'; | ||||
| import { useStream } from '@/stream.js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| import { $i } from '@/account.js'; | ||||
| import { defaultStore } from '@/store.js'; | ||||
| import { uploadFile } from '@/scripts/upload.js'; | ||||
| import type { MenuItem } from '@/types/menu.js'; | ||||
| 
 | ||||
| export function chooseFileFromPc(multiple: boolean, keepOriginal = false): Promise<Misskey.entities.DriveFile[]> { | ||||
| export function chooseFileFromPc(multiple: boolean, keepOriginal = false, useWatermark = false): Promise<Misskey.entities.DriveFile[]> { | ||||
| 	return new Promise((res, rej) => { | ||||
| 		const input = document.createElement('input'); | ||||
| 		input.type = 'file'; | ||||
| 		input.multiple = multiple; | ||||
| 		input.onchange = () => { | ||||
| 			if (!input.files) return res([]); | ||||
| 			const promises = Array.from(input.files, file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal)); | ||||
| 			const promises = Array.from(input.files, file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal, useWatermark)); | ||||
| 
 | ||||
| 			Promise.all(promises).then(driveFiles => { | ||||
| 				res(driveFiles); | ||||
|  | @ -83,6 +85,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> { | |||
| function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> { | ||||
| 	return new Promise((res, rej) => { | ||||
| 		const keepOriginal = ref(defaultStore.state.keepOriginalUploading); | ||||
| 		const useWatermark = ref(defaultStore.state.useWatermark); | ||||
| 
 | ||||
| 		os.popupMenu([label ? { | ||||
| 			text: label, | ||||
|  | @ -91,10 +94,15 @@ function select(src: HTMLElement | EventTarget | null, label: string | null, mul | |||
| 			type: 'switch', | ||||
| 			text: i18n.ts.keepOriginalUploading, | ||||
| 			ref: keepOriginal, | ||||
| 		}, { | ||||
| 		}, ...($i?.policies.canUseWatermark ? [{ | ||||
| 				type: 'switch', | ||||
| 				text: i18n.ts.useWatermark, | ||||
| 				ref: useWatermark, | ||||
| 			}] as MenuItem[] : [] | ||||
| 		), { | ||||
| 			text: i18n.ts.upload, | ||||
| 			icon: 'ti ti-upload', | ||||
| 			action: () => chooseFileFromPc(multiple, keepOriginal.value).then(files => res(files)), | ||||
| 			action: () => chooseFileFromPc(multiple, keepOriginal.value, useWatermark.value).then(files => res(files)), | ||||
| 		}, { | ||||
| 			text: i18n.ts.fromDrive, | ||||
| 			icon: 'ti ti-cloud', | ||||
|  |  | |||
|  | @ -35,6 +35,7 @@ export function uploadFile( | |||
| 	folder?: string | Misskey.entities.DriveFolder, | ||||
| 	name?: string, | ||||
| 	keepOriginal: boolean = defaultStore.state.keepOriginalUploading, | ||||
| 	watermark: boolean = defaultStore.state.useWatermark, | ||||
| ): Promise<Misskey.entities.DriveFile> { | ||||
| 	if ($i == null) throw new Error('Not logged in'); | ||||
| 
 | ||||
|  |  | |||
|  | @ -474,6 +474,30 @@ export const defaultStore = markRaw(new Storage('base', { | |||
| 		where: 'device', | ||||
| 		default: true, | ||||
| 	}, | ||||
| 	useWatermark: { | ||||
| 		where: 'device', | ||||
| 		default: false, | ||||
| 	}, | ||||
| 	watermarkConfig: { | ||||
| 		where: 'account', | ||||
| 		default: null as { | ||||
| 			fileId: string | null; | ||||
| 			fileUrl: string | null; | ||||
| 			width: number | null; | ||||
| 			height: number | null; | ||||
| 			enlargement: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad'; | ||||
| 			gravity: 'auto' | 'left' | 'right' | 'top' | 'bottom'; | ||||
| 			opacity: number; | ||||
| 			repeat: true | false | 'x' | 'y'; | ||||
| 			anchor: 'center' | 'top' | 'left' | 'bottom' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; | ||||
| 			offsetTop: number | null; | ||||
| 			offsetLeft: number | null; | ||||
| 			offsetBottom: number | null; | ||||
| 			offsetRight: number | null; | ||||
| 			backgroundColor: string | null; | ||||
| 			rotate: number | null; | ||||
| 		} | null, | ||||
| 	}, | ||||
| 
 | ||||
| 	sound_masterVolume: { | ||||
| 		where: 'device', | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue