This commit is contained in:
kakkokari-gtyih 2024-12-19 23:19:19 +09:00
parent 9fd07c0823
commit 499a6d3598
7 changed files with 108 additions and 20 deletions

30
locales/index.d.ts vendored
View file

@ -1906,14 +1906,22 @@ export interface Locale extends ILocale {
* *
*/ */
"watermark": string; "watermark": string;
/**
*
*/
"watermarkConfirm": string;
/** /**
* *
*/ */
"useWatermark": string; "useWatermark": string;
/** /**
* *
*/ */
"useWatermarkDescription": string; "useWatermarkDescription": string;
/**
*
*/
"useWatermarkInfo": string;
/** /**
* *
*/ */
@ -5282,6 +5290,14 @@ export interface Locale extends ILocale {
* *
*/ */
"acknowledgeNotesAndEnable": string; "acknowledgeNotesAndEnable": string;
/**
*
*/
"alwaysConfirm": string;
/**
*
*/
"useDefaultSettings": string;
"_accountSettings": { "_accountSettings": {
/** /**
* *
@ -10662,6 +10678,10 @@ export interface Locale extends ILocale {
"sent": string; "sent": string;
}; };
"_watermarkEditor": { "_watermarkEditor": {
/**
*
*/
"title": string;
/** /**
* *
*/ */
@ -10678,6 +10698,10 @@ export interface Locale extends ILocale {
* *
*/ */
"settingInvalidWarnDescription": string; "settingInvalidWarnDescription": string;
/**
*
*/
"useSmallFile": string;
/** /**
* *
*/ */
@ -10698,6 +10722,10 @@ export interface Locale extends ILocale {
* *
*/ */
"preserveBoundingRectDescription": string; "preserveBoundingRectDescription": string;
/**
*
*/
"clipboardUploadBehavior": string;
}; };
} }
declare const locales: { declare const locales: {

View file

@ -472,8 +472,10 @@ notFound: "見つかりません"
notFoundDescription: "指定されたURLに該当するページはありませんでした。" notFoundDescription: "指定されたURLに該当するページはありませんでした。"
uploadFolder: "既定アップロード先" uploadFolder: "既定アップロード先"
watermark: "ウォーターマーク" watermark: "ウォーターマーク"
watermarkConfirm: "ウォーターマークをつけますか?"
useWatermark: "ウォーターマークをつける" useWatermark: "ウォーターマークをつける"
useWatermarkDescription: "画像にウォーターマークを追加します" useWatermarkDescription: "画像のアップロード時にデフォルトでウォーターマークをつけるようにします。"
useWatermarkInfo: "デフォルトの値にかかわらず、アップロードメニューの「ウォーターマークをつける」スイッチを操作して、一回限りの設定を適用することができます。"
markAsReadAllNotifications: "すべての通知を既読にする" markAsReadAllNotifications: "すべての通知を既読にする"
markAsReadAllUnreadNotes: "すべての投稿を既読にする" markAsReadAllUnreadNotes: "すべての投稿を既読にする"
markAsReadAllTalkMessages: "すべてのチャットを既読にする" markAsReadAllTalkMessages: "すべてのチャットを既読にする"
@ -1316,6 +1318,8 @@ lockdown: "ロックダウン"
pleaseSelectAccount: "アカウントを選択してください" pleaseSelectAccount: "アカウントを選択してください"
availableRoles: "利用可能なロール" availableRoles: "利用可能なロール"
acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。" acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。"
alwaysConfirm: "常に確認する"
useDefaultSettings: "デフォルトの設定を適用する"
_accountSettings: _accountSettings:
requireSigninToViewContents: "コンテンツの表示にログインを必須にする" requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
@ -2843,12 +2847,15 @@ _followRequest:
sent: "送った申請" sent: "送った申請"
_watermarkEditor: _watermarkEditor:
title: "ウォーターマークをカスタマイズ"
driveFileTypeWarn: "このファイルは対応していません" driveFileTypeWarn: "このファイルは対応していません"
driveFileTypeWarnDescription: "画像ファイルを選択してください" driveFileTypeWarnDescription: "画像ファイルを選択してください"
settingInvalidWarn: "設定が不十分です" settingInvalidWarn: "設定が不十分です"
settingInvalidWarnDescription: "プレビューが正常に表示されることを確認してから保存してください" settingInvalidWarnDescription: "プレビューが正常に表示されることを確認してから保存してください"
useSmallFile: "ウォーターマーク用画像のファイルサイズが大きいと、処理の際にウォーターマークを読み込む時間が長くなり、アップロードに時間がかかるようになります。あらかじめ解像度を低くしたり、ファイルを圧縮したりしておくことを推奨します。"
repeatSetting: "描画モード" repeatSetting: "描画モード"
repeat: "全体を埋め尽くす" repeat: "全体を埋め尽くす"
padding: "余白" padding: "余白"
preserveBoundingRect: "回転した分の面積を確保する" preserveBoundingRect: "回転した分の面積を確保する"
preserveBoundingRectDescription: "通常はオンで問題ありません。ウォーターマークを回転させた際に余白が不自然になった場合はオフにしてみてください。" preserveBoundingRectDescription: "通常はオンで問題ありません。ウォーターマークを回転させた際に余白が不自然になった場合はオフにしてみてください。"
clipboardUploadBehavior: "クリップボード経由でのアップロード時の動作"

View file

@ -442,10 +442,10 @@ function replaceFile(file: Misskey.entities.DriveFile, newFile: Misskey.entities
files.value[files.value.findIndex(x => x.id === file.id)] = newFile; files.value[files.value.findIndex(x => x.id === file.id)] = newFile;
} }
function upload(file: File, name?: string): void { function upload(file: File, name?: string, watermark?: boolean): void {
if (props.mock) return; if (props.mock) return;
uploadFile(file, defaultStore.state.uploadFolder, name).then(res => { uploadFile(file, defaultStore.state.uploadFolder, name, undefined, watermark).then(res => {
files.value.push(res); files.value.push(res);
}); });
} }
@ -587,6 +587,8 @@ async function onPaste(ev: ClipboardEvent) {
if (props.mock) return; if (props.mock) return;
if (!ev.clipboardData) return; if (!ev.clipboardData) return;
let shouldApplyWatermark: boolean | undefined = undefined;
for (const { item, i } of Array.from(ev.clipboardData.items, (data, x) => ({ item: data, i: x }))) { for (const { item, i } of Array.from(ev.clipboardData.items, (data, x) => ({ item: data, i: x }))) {
if (item.kind === 'file') { if (item.kind === 'file') {
const file = item.getAsFile(); const file = item.getAsFile();
@ -594,7 +596,20 @@ async function onPaste(ev: ClipboardEvent) {
const lio = file.name.lastIndexOf('.'); const lio = file.name.lastIndexOf('.');
const ext = lio >= 0 ? file.name.slice(lio) : ''; const ext = lio >= 0 ? file.name.slice(lio) : '';
const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`; const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`;
upload(file, formatted);
if (file.type.startsWith('image/')) {
if (shouldApplyWatermark == null && defaultStore.state.clipboardWatermarkBehavior === 'confirm') {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.watermarkConfirm,
okText: i18n.ts.yes,
cancelText: i18n.ts.no,
});
shouldApplyWatermark = !canceled;
}
}
upload(file, formatted, shouldApplyWatermark);
} }
} }

View file

@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
@ok="save()" @ok="save()"
@closed="emit('closed')" @closed="emit('closed')"
> >
<template #header><i class="ti ti-ripple"></i> {{ i18n.ts.watermark }}</template> <template #header><i class="ti ti-ripple"></i> {{ i18n.ts._watermarkEditor.title }}</template>
<div :class="$style.watermarkEditorRoot"> <div :class="$style.watermarkEditorRoot">
<div :class="$style.watermarkEditorInputRoot"> <div :class="$style.watermarkEditorInputRoot">
@ -26,10 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</div> </div>
<div :class="$style.watermarkEditorSettings" class="_gaps"> <div :class="$style.watermarkEditorSettings" class="_gaps">
<MkSwitch v-model="useWatermark"> <MkInfo warn>{{ i18n.ts._watermarkEditor.useSmallFile }}</MkInfo>
<template #label>{{ i18n.ts.useWatermark }}</template>
<template #caption>{{ i18n.ts.useWatermarkDescription }}</template>
</MkSwitch>
<div> <div>
<div :class="$style.formLabel">{{ i18n.ts.watermark }}</div> <div :class="$style.formLabel">{{ i18n.ts.watermark }}</div>
@ -107,6 +104,7 @@ import MkRadios from '@/components/MkRadios.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkRange from '@/components/MkRange.vue'; import MkRange from '@/components/MkRange.vue';
import MkInfo from '@/components/MkInfo.vue';
import XAnchorSelector from '@/components/MkWatermarkEditorDialog.anchor.vue'; import XAnchorSelector from '@/components/MkWatermarkEditorDialog.anchor.vue';
import XPaddingView from '@/components/MkWatermarkEditorDialog.padding.vue'; import XPaddingView from '@/components/MkWatermarkEditorDialog.padding.vue';
@ -135,7 +133,6 @@ function cancel() {
//#endregion //#endregion
//#region //#region
const useWatermark = computed(defaultStore.makeGetterSetter('useWatermark'));
const watermarkConfig = ref<WatermarkUserConfig>(defaultStore.state.watermarkConfig ?? { const watermarkConfig = ref<WatermarkUserConfig>(defaultStore.state.watermarkConfig ?? {
opacity: 0.2, opacity: 0.2,
repeat: true, repeat: true,
@ -279,11 +276,11 @@ function chooseFile(ev: MouseEvent) {
const canvasLoading = ref(true); const canvasLoading = ref(true);
const canvasEl = useTemplateRef('canvasEl'); const canvasEl = useTemplateRef('canvasEl');
onMounted(() => { onMounted(() => {
watch([useWatermark, watermarkConfig], ([useWatermarkTo, watermarkConfigTo]) => { watch(watermarkConfig, (watermarkConfigTo) => {
canvasLoading.value = true; canvasLoading.value = true;
if (canvasEl.value) { if (canvasEl.value) {
// @/scripts/watermark.ts DEFAULT_ASPECT_RATIO 使 // @/scripts/watermark.ts DEFAULT_ASPECT_RATIO 使
applyWatermark('/client-assets/hill.webp', canvasEl.value, useWatermarkTo && canPreview(watermarkConfigTo) ? watermarkConfigTo : null).then(() => { applyWatermark('/client-assets/hill.webp', canvasEl.value, canPreview(watermarkConfigTo) ? watermarkConfigTo : null).then(() => {
canvasLoading.value = false; canvasLoading.value = false;
}); });
} }

View file

@ -41,11 +41,34 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #icon><i class="ti ti-file-shredder"></i></template> <template #icon><i class="ti ti-file-shredder"></i></template>
{{ i18n.ts.drivecleaner }} {{ i18n.ts.drivecleaner }}
</FormLink> </FormLink>
<FormLink @click="openWatermarkEditor"> <MkFolder>
<template #icon><i class="ti ti-ripple"></i></template> <template #icon><i class="ti ti-ripple"></i></template>
{{ i18n.ts.watermark }} <template #label>{{ i18n.ts.watermark }}</template>
<template #suffix>{{ defaultStore.reactiveState.useWatermark.value ? i18n.ts.enabled : i18n.ts.disabled }}</template>
<div>
<div class="_gaps">
<MkInfo>{{ i18n.ts.useWatermarkInfo }}</MkInfo>
<MkSwitch v-model="useWatermark">
<template #label>{{ i18n.ts.useWatermark }}</template>
<template #caption>{{ i18n.ts.useWatermarkDescription }}</template>
</MkSwitch>
<MkSelect v-model="clipboardWatermarkBehavior">
<template #label>{{ i18n.ts._watermarkEditor.clipboardUploadBehavior }}</template>
<option value="confirm">{{ i18n.ts.alwaysConfirm }}</option>
<option value="default">{{ i18n.ts.useDefaultSettings }}</option>
</MkSelect>
</div>
<hr/>
<FormLink @click="openWatermarkEditor">
<template #icon><i class="ti ti-pencil"></i></template>
{{ i18n.ts._watermarkEditor.title }}
</FormLink> </FormLink>
</div>
</MkFolder>
<MkSwitch v-model="keepOriginalUploading"> <MkSwitch v-model="keepOriginalUploading">
<template #label>{{ i18n.ts.keepOriginalUploading }}</template> <template #label>{{ i18n.ts.keepOriginalUploading }}</template>
<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template> <template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template>
@ -67,10 +90,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, defineAsyncComponent, ref } from 'vue'; import { computed, defineAsyncComponent, ref, watch } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import FormLink from '@/components/form/link.vue'; import FormLink from '@/components/form/link.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
import FormSection from '@/components/form/section.vue'; import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/MkKeyValue.vue'; import MkKeyValue from '@/components/MkKeyValue.vue';
@ -83,6 +109,7 @@ import MkChart from '@/components/MkChart.vue';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import { signinRequired } from '@/account.js'; import { signinRequired } from '@/account.js';
import { reloadAsk } from '@/scripts/reload-ask';
const $i = signinRequired(); const $i = signinRequired();
@ -105,9 +132,19 @@ const meterStyle = computed(() => {
}; };
}); });
const useWatermark = computed(defaultStore.makeGetterSetter('useWatermark'));
const clipboardWatermarkBehavior = computed(defaultStore.makeGetterSetter('clipboardWatermarkBehavior'));
const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading')); const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading'));
const keepOriginalFilename = computed(defaultStore.makeGetterSetter('keepOriginalFilename')); const keepOriginalFilename = computed(defaultStore.makeGetterSetter('keepOriginalFilename'));
watch([
useWatermark,
clipboardWatermarkBehavior,
], () => {
reloadAsk({ unison: true, reason: i18n.ts.reloadRequiredToApplySettings });
});
misskeyApi('drive').then(info => { misskeyApi('drive').then(info => {
capacity.value = info.capacity; capacity.value = info.capacity;
usage.value = info.usage; usage.value = info.usage;

View file

@ -33,7 +33,7 @@ const mimeTypeMap = {
export function uploadFile( export function uploadFile(
file: File, file: File,
folder?: string | Misskey.entities.DriveFolder, folder?: string | null | Misskey.entities.DriveFolder,
name?: string, name?: string,
keepOriginal: boolean = defaultStore.state.keepOriginalUploading, keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
watermark: boolean = defaultStore.state.useWatermark, watermark: boolean = defaultStore.state.useWatermark,

View file

@ -479,6 +479,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device', where: 'device',
default: false, default: false,
}, },
clipboardWatermarkBehavior: {
where: 'device',
default: 'default' as 'default' | 'confirm',
},
watermarkConfig: { watermarkConfig: {
where: 'account', where: 'account',
default: null as WatermarkUserConfig | null, default: null as WatermarkUserConfig | null,