From b96d9c6973b1c861306fdb9f51256cee5325a2b1 Mon Sep 17 00:00:00 2001
From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Sat, 30 Mar 2024 16:02:03 +0900
Subject: fix/enhance(frontend): 映像・音声周りの改修 (#13206)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* enhance(frontend): 映像・音声周りの改修

* fix

* fix design

* fix lint

* キーボードショートカットを整備

* Update Changelog

* fix

* feat: ループ再生

* ネイティブの動作と同期されるように

* Update Changelog

* key指定を消す
---
 packages/frontend/src/components/MkMenu.vue | 91 +++++++++++++++++++++++++++--
 1 file changed, 86 insertions(+), 5 deletions(-)

(limited to 'packages/frontend/src/components/MkMenu.vue')

diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index faed6416d0..d91239b9e2 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -42,9 +42,26 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</button>
 			<button v-else-if="item.type === 'switch'" role="menuitemcheckbox" :tabindex="i" class="_button" :class="[$style.item, $style.switch, { [$style.switchDisabled]: item.disabled } ]" @click="switchItem(item)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
-				<MkSwitchButton :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
+				<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
+				<MkSwitchButton v-else :class="$style.switchButton" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
+				<div :class="$style.item_content">
+					<span :class="[$style.item_content_text, { [$style.switchText]: !item.icon }]">{{ item.text }}</span>
+					<MkSwitchButton v-if="item.icon" :class="[$style.switchButton, $style.caret]" :checked="item.ref" :disabled="item.disabled" @toggle="switchItem(item)"/>
+				</div>
+			</button>
+			<button v-else-if="item.type === 'radio'" class="_button" role="menuitem" :tabindex="i" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="preferClick ? null : showRadioOptions(item, $event)" @click="!preferClick ? null : showRadioOptions(item, $event)">
+				<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]" style="pointer-events: none;"></i>
 				<div :class="$style.item_content">
-					<span :class="[$style.item_content_text, $style.switchText]">{{ item.text }}</span>
+					<span :class="$style.item_content_text" style="pointer-events: none;">{{ item.text }}</span>
+					<span :class="$style.caret" style="pointer-events: none;"><i class="ti ti-chevron-right ti-fw"></i></span>
+				</div>
+			</button>
+			<button v-else-if="item.type === 'radioOption'" :tabindex="i" class="_button" role="menuitem" :class="[$style.item, { [$style.radioActive]: item.active }]" @click="clicked(item.action, $event, false)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
+				<div :class="$style.icon">
+					<span :class="[$style.radio, { [$style.radioChecked]: item.active }]"></span>
+				</div>
+				<div :class="$style.item_content">
+					<span :class="$style.item_content_text">{{ item.text }}</span>
 				</div>
 			</button>
 			<button v-else-if="item.type === 'parent'" class="_button" role="menuitem" :tabindex="i" :class="[$style.item, $style.parent, { [$style.childShowing]: childShowingItem === item }]" @mouseenter="preferClick ? null : showChildren(item, $event)" @click="!preferClick ? null : showChildren(item, $event)">
@@ -77,7 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ComputedRef, computed, defineAsyncComponent, isRef, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
 import { focusPrev, focusNext } from '@/scripts/focus.js';
 import MkSwitchButton from '@/components/MkSwitch.button.vue';
-import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu.js';
+import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { isTouchUsing } from '@/scripts/touch.js';
@@ -168,6 +185,31 @@ function onItemMouseLeave(item) {
 	if (childCloseTimer) window.clearTimeout(childCloseTimer);
 }
 
+async function showRadioOptions(item: MenuRadio, ev: MouseEvent) {
+	const children: MenuItem[] = Object.keys(item.options).map<MenuRadioOption>(key => {
+		const value = item.options[key];
+		return {
+			type: 'radioOption',
+			text: key,
+			action: () => {
+				item.ref = value;
+			},
+			active: computed(() => item.ref === value),
+		};
+	});
+
+	if (props.asDrawer) {
+		os.popupMenu(children, ev.currentTarget ?? ev.target).finally(() => {
+			emit('close');
+		});
+		emit('hide');
+	} else {
+		childTarget.value = (ev.currentTarget ?? ev.target) as HTMLElement;
+		childMenu.value = children;
+		childShowingItem.value = item;
+	}
+}
+
 async function showChildren(item: MenuParent, ev: MouseEvent) {
 	const children: MenuItem[] = await (async () => {
 		if (childrenCache.has(item)) {
@@ -196,8 +238,10 @@ async function showChildren(item: MenuParent, ev: MouseEvent) {
 	}
 }
 
-function clicked(fn: MenuAction, ev: MouseEvent) {
+function clicked(fn: MenuAction, ev: MouseEvent, doClose = true) {
 	fn(ev);
+
+	if (!doClose) return;
 	close(true);
 }
 
@@ -350,6 +394,15 @@ onBeforeUnmount(() => {
 		}
 	}
 
+	&.radioActive {
+		color: var(--accent) !important;
+		opacity: 1;
+
+		&:before {
+			background-color: var(--accentedBg) !important;
+		}
+	}
+
 	&:not(:active):focus-visible {
 		box-shadow: 0 0 0 2px var(--focus) inset;
 	}
@@ -417,11 +470,11 @@ onBeforeUnmount(() => {
 
 .switchButton {
 	margin-left: -2px;
+	--height: 1.35em;
 }
 
 .switchText {
 	margin-left: 8px;
-	margin-top: 2px;
 	overflow: hidden;
 	text-overflow: ellipsis;
 }
@@ -461,4 +514,32 @@ onBeforeUnmount(() => {
 	margin: 8px 0;
 	border-top: solid 0.5px var(--divider);
 }
+
+.radio {
+	display: inline-block;
+	position: relative;
+	width: 1em;
+	height: 1em;
+	vertical-align: -.125em;
+	border-radius: 50%;
+	border: solid 2px var(--divider);
+	background-color: var(--panel);
+
+	&.radioChecked {
+		border-color: var(--accent);
+
+		&::after {
+			content: "";
+			display: block;
+			position: absolute;
+			top: 50%;
+			left: 50%;
+			transform: translate(-50%, -50%);
+			width: 50%;
+			height: 50%;
+			border-radius: 50%;
+			background-color: var(--accent);
+		}
+	}
+}
 </style>
-- 
cgit v1.2.3-freya

