From a0fc59bd4b366518edb47254bd7052888b56efd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=82=8F=E3=82=8F=E3=82=8F=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Mon, 16 Dec 2024 09:33:59 +0900 Subject: [PATCH] watermark wip --- locales/index.d.ts | 64 +++++++++++++++++- locales/ja-JP.yml | 17 ++++- packages/frontend-shared/js/const.ts | 1 + .../frontend/assets/default-watermark.png | Bin 0 -> 1880 bytes packages/frontend/src/components/MkDrive.vue | 11 ++- packages/frontend/src/pages/admin/roles.vue | 16 ++--- .../frontend/src/pages/settings/drive.vue | 33 ++++++++- packages/frontend/src/scripts/select-file.ts | 16 +++-- packages/frontend/src/scripts/upload.ts | 1 + packages/frontend/src/store.ts | 24 +++++++ 10 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 packages/frontend/assets/default-watermark.png diff --git a/locales/index.d.ts b/locales/index.d.ts index 0ae188f1f7..beccac4a1d 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -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; /** * サーバールール */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1b59708d85..01fdd36abe 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -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: "続けるには、全ての「同意する」にチェックが入っている必要があります。" diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index 4fe5cbb205..7253bc180c 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -86,6 +86,7 @@ export const ROLE_POLICIES = [ 'canManageCustomEmojis', 'canManageAvatarDecorations', 'canSearchNotes', + 'canUseReaction', 'canUseTranslator', 'canHideAds', 'driveCapacityMb', diff --git a/packages/frontend/assets/default-watermark.png b/packages/frontend/assets/default-watermark.png new file mode 100644 index 0000000000000000000000000000000000000000..e8edd64f69bebf928fdfe82eec48e6391465d722 GIT binary patch literal 1880 zcmV-e2dDT_Nk&Fc2LJ$9MM6+kP&iCO2LJ#s4M7JG_n{_|BxT~eXO7;#P^ zG2TOj+D4Mv+{Abf5enP3Z85XkA0UA7-ax^JSp*Lf4hCv{ct4u|>-Q>aNn;4D@wIZL zaHX(0Gbu4C(HNPONC-4WkRaQ3+ct3TMRpu1y+{dN;yjXI1Nc}>528FtS;B<^`#LyQhaW=o8uUhXJ7sTyOl8~?CiosYni-? zi3`=*{mI;1+n-Md@ufPsr))vpy-=84# z{;0Bi-oxwb59@DB&Gn3_0+wvvgi-%c|+du@dQJ$jnAat51Z5gxLa) zyTv9P+FpQ*L(A5PHjToc6S?65kvDHulfwvaT3)JkS$^g4C&+v+2?R!!j0}nmJ zsP&L0RffUNTXD`cPO3mV#wrIc6c)`3ISg9FVKq}uzHpRe@Gk~VMK2PBP^L>Cl&(R! zCRpMsP2NLBF7<8tr2LR2`MW6e&p!C?k8Zlu=AF6QT#oyd|B=^!R= zvT*^GK}Y#Vz5hpyY8>0H@!No@vTWtPgDtDiLix_BxVQs#M&V#j+?0OBWWd{v~bQfB_R zC`vIZ<>42?UNiB>kRFvSk)`s|5Jq*Ydus!XQOs#&2!gA{ecL66)pM?V(PFYFIu-r?F$Cdf>%GuvlJHqFxEm zFruoN#MhXpI$_*(1oxStn8lIo8X}*g*e$u0gECK08bpxEnDywbS6;sEVi&`9BHi~G zmdSg$)_9O3MZwp&7LrW1T^ZU61qw%vI{Qb~cnkU1)j5ix&VX!aQ-GF~tkeyu!d8OX z)fG)$+Of6z?sPf@E@?NTOM%OnAIs!5Du-Nj+S$oF9+SCyb6;&h9vdrXekFCDju$R* zDu8=)Lv0^%+W>J+QrbM;9f5X!b+dUE+Gq#ZR%YowNes*66^klM?Yhyu)g|!GF7%od zDTVVF6FEkgn57eAV(tmCk!Niy>LzffWMvZQQVOtMuMwsHS0+XaMgNP|3P|u zMnHcM)YlE>>Jv{+-|_yq+Trh)+nZnie%<=%S@LwC-d#Q3OipeRs`w+_Yh(+bx1ZP7 zr`|YTpZsLzhsg7TY(2bAd^s~QS}3m)IJqKEJ7Bjt5oW%cgQwlZPS%8OXeJMj?}CrU zoPG5MPd3{)O$mf9A_i9z=j7=6`XY3zS}}eayg_}*wob1PO_UbP%NDL9%-w$eK=;&x SH95IIQEr2a{O6OwIWhn;$D-;0 literal 0 HcmV?d00001 diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index 8be6d6f53d..fe44c33c6b 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -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([]); const uploadings = uploads; const connection = useStream().useChannel('drive'); const keepOriginal = ref(defaultStore.state.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい +const useWatermark = ref(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', }, { diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index b1cbdad137..cc1546e0ef 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -89,14 +89,6 @@ SPDX-License-Identifier: AGPL-3.0-only - - - - - - - - @@ -105,6 +97,14 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + + + diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index 0e66b93f1c..1eeb14b854 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -33,11 +33,36 @@ SPDX-License-Identifier: AGPL-3.0-only
+ {{ i18n.ts.uploadFolder }} - + + + +
+ + + + + + +
+
+ +
+
+ +
+ {{ i18n.ts.selectFile }} + {{ i18n.ts.defa }} +
+
+
+
+
+ {{ i18n.ts.drivecleaner }} @@ -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; diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts index b037aa8acc..d0d874d428 100644 --- a/packages/frontend/src/scripts/select-file.ts +++ b/packages/frontend/src/scripts/select-file.ts @@ -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 { +export function chooseFileFromPc(multiple: boolean, keepOriginal = false, useWatermark = false): Promise { 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 { function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise { 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', diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts index 713573a377..e9991ba69c 100644 --- a/packages/frontend/src/scripts/upload.ts +++ b/packages/frontend/src/scripts/upload.ts @@ -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 { if ($i == null) throw new Error('Not logged in'); diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 1d981e897b..3b2ac3b61d 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -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',