Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 127 additions & 25 deletions src/components/Menu/MenuBar/MenuBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,72 @@
class="mac-traffic-light-space"
></div>
<img v-else src="/icon.png" class="window-logo" alt="application logo" />
<MenuButton
v-for="(root, index) of menudata"
:key="index"
v-model:selected="subMenuOpenFlags[index]"
:menudata="root"
:disable="
menubarLocked || (root.disableWhenUiLocked && uiLocked) || root.disabled
"
@mouseover="reassignSubMenuOpen(index)"
@mouseleave="
root.type === 'button' ? (subMenuOpenFlags[index] = false) : undefined
"
/>
<QSpace />
<div class="window-title" :class="{ 'text-warning': isMultiEngineOffMode }">
{{ titleText }}
<div class="no-margin row col no-wrap items-center">
<div
ref="button-container"
class="button-container row no-wrap items-center overflow-hidden"
>
<div class="row other-menu-target-0"></div>
<div
v-for="(root, index) of menudata"
:key="index"
v-intersection="intersection"
:data-index="index"
:class="[
{ invisible: isHidden[index] },
`other-menu-target-${index + 1}`,
]"
class="row no-wrap"
>
<MenuButton
v-model:selected="subMenuOpenFlags[index]"
style="flex: none"
:menudata="root"
:disable="
menubarLocked ||
(root.disableWhenUiLocked && uiLocked) ||
root.disabled
"
@mouseover="reassignSubMenuOpen(index)"
@mouseleave="
root.type === 'button'
? (subMenuOpenFlags[index] = false)
: undefined
"
/>
</div>
<Teleport v-if="isHidden.some((v) => v)" defer :to="otherMenuTo">
<MenuButton
v-model:selected="otherMenuOpen"
aria-label="その他"
:icon="isHidden.every((v) => v) ? 'menu' : 'more_horiz'"
style="flex: none"
:menudata="otherMenudata"
:disable="
menubarLocked ||
(otherMenudata.disableWhenUiLocked && uiLocked) ||
otherMenudata.disabled
"
@mouseover="reassignOtherMenuOpen"
/></Teleport>
</div>
<div
class="window-title"
:class="{ 'text-warning': isMultiEngineOffMode }"
>
{{ titleText }}
</div>
</div>
<div class="no-margin row items-center no-wrap">
<TitleBarEditorSwitcher />
<TitleBarButtons />
</div>
<QSpace />
<TitleBarEditorSwitcher />
<TitleBarButtons />
</QBar>
</template>

<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { useQuasar } from "quasar";
import { computed, ref, useTemplateRef, watch } from "vue";
import { IntersectionValue, useQuasar } from "quasar";
import { MenuItemButton, MenuItemData, MenuItemRoot } from "../type";
import MenuButton from "../MenuButton.vue";
import { MenuBarCategory } from "./menuBarData";
Expand Down Expand Up @@ -149,7 +189,8 @@ const subMenuOpenFlags = ref(

const reassignSubMenuOpen = (idx: number) => {
if (subMenuOpenFlags.value[idx]) return;
if (subMenuOpenFlags.value.find((x) => x)) {
if (otherMenuOpen.value || subMenuOpenFlags.value.find((x) => x)) {
otherMenuOpen.value = false;
const arr = [...Array(menudata.value.length)].map(() => false);
arr[idx] = true;
subMenuOpenFlags.value = arr;
Expand All @@ -162,6 +203,60 @@ watch(uiLocked, () => {
subMenuOpenFlags.value = [...Array(menudata.value.length)].map(() => false);
}
});

// 省略されたメニューの処理
const isHidden = ref(menudata.value.map(() => false));
const otherMenuOpen = ref(false);
const otherMenudata = computed<MenuItemRoot>(() => {
return {
type: "root",
disableWhenUiLocked: false,
subMenu: menudata.value.filter((_, i) => isHidden.value[i]),
};
});
const otherMenuTo = computed(() => {
const i = isHidden.value.findIndex((v) => v);
return `.other-menu-target-${i}`;
});
const buttonContainer = useTemplateRef("button-container");
const intersection: IntersectionValue = {
handler(entry) {
const element = entry?.target;
if (entry == undefined || !(element instanceof HTMLElement)) {
throw new Error("Intersection observer target is not an HTMLElement");
}
if (element.dataset.index == undefined) {
throw new Error(
"Menu button element missing required data-index attribute",
);
}
isHidden.value[parseInt(element.dataset.index)] = !entry.isIntersecting;
Copy link

Copilot AI Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseInt without a radix parameter can lead to unexpected behavior. Use parseInt(element.dataset.index, 10) to explicitly specify base 10.

Suggested change
isHidden.value[parseInt(element.dataset.index)] = !entry.isIntersecting;
isHidden.value[parseInt(element.dataset.index, 10)] = !entry.isIntersecting;

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これはなくてもいい気がします。
element.dataset.indexの値はMenubar.vue内で決まるので想定外の文字列が来ること自体を想定する必要はないと思います。
また、想定外の値が来ることを想定するなら値が配列の範囲内かの検証までやらないと不十分だと思います。

return true;
},
cfg: {
root: buttonContainer.value ?? undefined,
threshold: [1],
},
};
const reassignOtherMenuOpen = () => {
if (subMenuOpenFlags.value.some((v) => v)) {
subMenuOpenFlags.value.fill(false);
otherMenuOpen.value = true;
}
};
watch(
isHidden,
(isHidden) => {
isHidden.forEach((v, i) => {
if (v) {
subMenuOpenFlags.value[i] = false; // 省略されたメニューは閉じる
}
});
// 省略されたメニューの内容が変化したときは常に閉じる
otherMenuOpen.value = false;
},
{ deep: true },
);
</script>

<style lang="scss">
Expand All @@ -180,7 +275,6 @@ watch(uiLocked, () => {
min-height: vars.$menubar-height;
-webkit-app-region: drag; // Electronのドラッグ領域
:deep(.q-btn) {
margin-left: 0;
-webkit-app-region: no-drag; // Electronのドラッグ領域対象から外す
}
}
Expand All @@ -189,11 +283,19 @@ watch(uiLocked, () => {
height: vars.$menubar-height;
}

.button-container {
flex: none;
max-width: 50%;
}

.window-title {
flex: 1 max-content;
height: vars.$menubar-height;
margin-right: 10%;
text-overflow: ellipsis;
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
-webkit-app-region: drag;
}

.mac-traffic-light-space {
Expand Down
1 change: 1 addition & 0 deletions src/components/Menu/MenuButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const subMenuOpenFlags = ref(

const isDisabledMenuItem = computed(() => (menu: MenuItemData) => {
if (menu.type === "separator") return false;
if (menu.disabled) return true;
if (menu.disableWhenUiLocked && uiLocked.value) return true;
if (menu.disableWhileReloadingLock && reloadingLocked.value) return true;
return false;
Expand Down
8 changes: 7 additions & 1 deletion src/components/Menu/MenuItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,18 @@
anchor="top end"
transitionShow="none"
transitionHide="none"
:target="!uiLocked"
>
<MenuItem
v-for="(menu, i) of menudata.subMenu"
:key="i"
v-model:selected="subMenuOpenFlags[i]"
:menudata="menu"
:disable="
menu.type !== 'separator' &&
(menu.disabled ||
(menu.disableWhenUiLocked && uiLocked) ||
(menu.disableWhileReloadingLock && reloadingLocked))
"
@mouseover="reassignSubMenuOpen(i)"
/>
</QMenu>
Expand Down Expand Up @@ -110,6 +115,7 @@ const getMenuBarHotkey = (rawLabel: string) => {
}
};
const uiLocked = computed(() => store.getters.UI_LOCKED);
const reloadingLocked = computed(() => store.state.reloadingLock);
const selectedComputed = computed({
get: () => props.selected,
set: (val) => emit("update:selected", val),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading