Skip to content

Commit 6d7b9be

Browse files
committed
feat(settings): rework context menu and add toggle settings
1 parent eeb80ee commit 6d7b9be

File tree

11 files changed

+195
-28
lines changed

11 files changed

+195
-28
lines changed

src/components/views/settings/SettingsComponent.vue

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import SettingsAccount from '~/components/views/settings/SettingsAccount.vue';
77
import SettingsCache from '~/components/views/settings/SettingsCache.vue';
88
import SettingsLinks from '~/components/views/settings/SettingsLinks.vue';
99
import SettingsLogs from '~/components/views/settings/SettingsLogs.vue';
10+
import SettingsMenus from '~/components/views/settings/SettingsMenus.vue';
1011
import SettingsTabs from '~/components/views/settings/SettingsTabs.vue';
1112
import { useI18n } from '~/utils';
1213
@@ -22,6 +23,7 @@ const sections: Section[] = [
2223
{ title: 'menu__account', reference: ref(), component: SettingsAccount },
2324
{ title: 'menu__tabs', reference: ref(), component: SettingsTabs },
2425
{ title: 'menu__links', reference: ref(), component: SettingsLinks },
26+
{ title: 'menu__menus', reference: ref(), component: SettingsMenus },
2527
{ title: 'menu__cache', reference: ref(), component: SettingsCache },
2628
{ title: 'menu__logs', reference: ref(), component: SettingsLogs },
2729
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<script lang="ts" setup>
2+
import { NSwitch } from 'naive-ui';
3+
4+
import SettingsFormItem from '~/components/views/settings/SettingsFormItem.vue';
5+
import {
6+
useContextMenuStore,
7+
useContextMenuStoreRefs,
8+
} from '~/stores/settings/context-menu.store';
9+
import { useI18n } from '~/utils';
10+
11+
const i18n = useI18n('settings', 'menus');
12+
13+
const { enabledContextMenus } = useContextMenuStoreRefs();
14+
const { toggleContextMenu } = useContextMenuStore();
15+
</script>
16+
17+
<template>
18+
<div class="menus-container">
19+
<!-- Enable tabs -->
20+
<SettingsFormItem
21+
v-for="[id, state] of enabledContextMenus"
22+
:key="id"
23+
:label="i18n(`label_menu__${ id }`)"
24+
>
25+
<NSwitch :value="state" class="form-switch" @update:value="toggleContextMenu(id)">
26+
<template #checked>{{ i18n('on', 'common', 'button') }}</template>
27+
<template #unchecked>{{ i18n('off', 'common', 'button') }}</template>
28+
</NSwitch>
29+
</SettingsFormItem>
30+
</div>
31+
</template>
32+
33+
<style lang="scss" scoped>
34+
.menus-container {
35+
display: flex;
36+
flex-direction: column;
37+
gap: 1.5rem;
38+
39+
.form-switch {
40+
display: flex;
41+
flex: 1 1 auto;
42+
justify-content: center;
43+
min-width: 5rem;
44+
padding: 0 0.5rem;
45+
font-size: 0.75rem;
46+
}
47+
}
48+
</style>

src/i18n/en/settings/settings-menu.json

+4
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,9 @@
1818
"settings__menu__links": {
1919
"message": "Links",
2020
"description": "Links menu item"
21+
},
22+
"settings__menu__menus": {
23+
"message": "Menus",
24+
"description": "Context menus menu item"
2125
}
2226
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"settings__menus__label_menu__open_in_side_trakt": {
3+
"message": "Open selection in Side Trakt",
4+
"description": "Label to toggle the 'Open selection in Side Trakt' menu item"
5+
},
6+
"settings__menus__label_menu__add_to_search_history": {
7+
"message": "Add selection to search history",
8+
"description": "Label to toggle the 'Add selection to search history' menu item"
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { RouteLocationNormalized } from 'vue-router';
2+
3+
import { ContextMenuConstants, ContextMenuId, type ContextMenuIds, ContextMenus } from '~/models/context/context-menu.model';
4+
import { Route, RouterStorageKey } from '~/models/router.model';
5+
import { action } from '~/utils/browser/borwser-action.utils';
6+
import { context, type ContextMenuOnClickedData } from '~/utils/browser/browser-context.utils';
7+
import { runtime } from '~/utils/browser/browser-runtime.utils';
8+
import { storage } from '~/utils/browser/browser-storage.utils';
9+
import { createTab } from '~/utils/browser/browser.utils';
10+
11+
const setLastRoute = (data: ContextMenuOnClickedData) => {
12+
if (!data?.selectionText) return;
13+
return storage.local.set(RouterStorageKey.LastRoute, {
14+
name: Route.Search,
15+
query: { search: data.selectionText },
16+
} satisfies Partial<RouteLocationNormalized>);
17+
};
18+
19+
const openPopup = async () => {
20+
if (action?.openPopup) return action.openPopup();
21+
if (!runtime) return;
22+
await createTab({
23+
url: runtime.getURL('views/options/index.html'),
24+
});
25+
};
26+
27+
export const ContextMenusHooks: Record<ContextMenuIds, (data: ContextMenuOnClickedData) => void | Promise<void>> = {
28+
[ContextMenuId.OpenInSideTrakt]: async data => {
29+
await setLastRoute(data);
30+
await openPopup();
31+
},
32+
[ContextMenuId.AddToSearchHistory]: setLastRoute,
33+
} as const;
34+
35+
export const installContextMenus = async (enabled?: Record<ContextMenuIds, boolean>) => {
36+
if (!context) return;
37+
if (!enabled) enabled = await storage.local.get<Record<ContextMenuIds, boolean>>(ContextMenuConstants.LocalEnabled);
38+
if (!enabled) return;
39+
40+
await context.removeAll();
41+
42+
return (Object.entries(enabled) as [ContextMenuIds, boolean][])
43+
.filter(([_, isEnabled]) => isEnabled)
44+
.map(([id]) => context!.create(ContextMenus[id]));
45+
};

src/models/context/context-menu.model.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
export const ContextMenuId = {
2-
OpenInSideTrakt: 'open-in-side-trakt',
3-
AddToSearchHistory: 'add-to-search-history',
2+
OpenInSideTrakt: 'open_in_side_trakt',
3+
AddToSearchHistory: 'add_to_search_history',
44
} as const;
55

66
export type ContextMenuIds = (typeof ContextMenuId)[keyof typeof ContextMenuId];
77

8+
const contextMenuIdValues = new Set<string>(Object.values(ContextMenuId));
9+
export const isContextMenuId = (id: unknown): id is ContextMenuIds => typeof id === 'string' && contextMenuIdValues.has(id);
10+
811
export type ContextMenu = {
912
id: ContextMenuIds;
1013
title: string;
@@ -23,3 +26,8 @@ export const ContextMenus: Record<ContextMenuIds, ContextMenu> = {
2326
contexts: ['selection'],
2427
},
2528
} as const;
29+
30+
export const ContextMenuConstants = {
31+
Store: 'settings.context-menu',
32+
LocalEnabled: 'settings.context-menu.enabled',
33+
};
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const MessageType = {
2+
ContextMenu: 'context-menu',
3+
} as const;

src/scripts/background/index.ts

+13-26
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,26 @@
1-
import type { RouteLocationNormalized } from 'vue-router';
2-
3-
import { ContextMenuId, ContextMenus } from '~/models/context/context-menu.model';
4-
import { Route, RouterStorageKey } from '~/models/router.model';
5-
import { action } from '~/utils/browser/borwser-action.utils';
1+
import { ContextMenusHooks, installContextMenus } from '~/models/context/context-menu-hooks.model';
2+
import { isContextMenuId } from '~/models/context/context-menu.model';
3+
import { MessageType } from '~/models/message/message-type.model';
64
import { context } from '~/utils/browser/browser-context.utils';
75
import { runtime } from '~/utils/browser/browser-runtime.utils';
8-
import { storage } from '~/utils/browser/browser-storage.utils';
9-
import { createTab } from '~/utils/browser/browser.utils';
106

117
console.debug('Background script started');
128

139
runtime?.onInstalled.addListener(async () => {
10+
await installContextMenus();
11+
1412
console.debug('Extension installed');
15-
if (!context) return;
16-
await Promise.all(Object.values(ContextMenus).map(m => context!.create(m)));
13+
});
1714

18-
console.debug('Context menus created');
15+
runtime?.onMessage.addListener(async message => {
16+
if (message.type === MessageType.ContextMenu) {
17+
await installContextMenus(message.enabled);
18+
}
1919
});
2020

2121
context?.onClicked.addListener(async info => {
22-
console.debug('Context menu event', info);
23-
24-
if (!info?.selectionText) return;
25-
await storage.local.set(RouterStorageKey.LastRoute, {
26-
name: Route.Search,
27-
query: { search: info.selectionText },
28-
} satisfies Partial<RouteLocationNormalized>);
29-
30-
if (info.menuItemId !== ContextMenuId.OpenInSideTrakt) return;
31-
32-
if (action?.openPopup) {
33-
await action.openPopup();
34-
} else if (runtime) {
35-
await createTab({
36-
url: runtime.getURL('views/options/index.html'),
37-
});
22+
if (isContextMenuId(info.menuItemId)) {
23+
return ContextMenusHooks[info.menuItemId](info);
3824
}
25+
console.error('Unknown context menu event', info);
3926
});
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { defineStore } from 'pinia';
2+
3+
import { computed, reactive, toRefs } from 'vue';
4+
5+
import { ContextMenuConstants, ContextMenuId, type ContextMenuIds } from '~/models/context/context-menu.model';
6+
import { MessageType } from '~/models/message/message-type.model';
7+
import { runtime } from '~/utils/browser/browser-runtime.utils';
8+
import { storage } from '~/utils/browser/browser-storage.utils';
9+
import { debounce } from '~/utils/debounce.utils';
10+
11+
type ContextMenuState = Record<ContextMenuIds, boolean>;
12+
13+
const defaultState = {
14+
[ContextMenuId.OpenInSideTrakt]: false,
15+
[ContextMenuId.AddToSearchHistory]: false,
16+
} as const;
17+
18+
export const useContextMenuStore = defineStore(ContextMenuConstants.Store, () => {
19+
const enabled = reactive<ContextMenuState>(defaultState);
20+
21+
const clearState = () => {
22+
Object.assign(enabled, defaultState);
23+
};
24+
25+
const saveState = debounce(async () => {
26+
await storage.local.set(ContextMenuConstants.LocalEnabled, enabled);
27+
await runtime?.sendMessage({ type: MessageType.ContextMenu, enabled });
28+
}, 500);
29+
30+
const restoreState = async () => {
31+
const state = await storage.local.get<ContextMenuState>(ContextMenuConstants.LocalEnabled);
32+
if (state) Object.assign(enabled, state);
33+
return state;
34+
};
35+
36+
const toggleContextMenu = (id: ContextMenuIds) => {
37+
enabled[id] = !enabled[id];
38+
saveState().catch(err => console.error('Failed to save context menu state', { id, err }));
39+
};
40+
41+
const initContextMenuStore = async () => {
42+
await restoreState();
43+
};
44+
45+
return {
46+
clearState,
47+
initContextMenuStore,
48+
toggleContextMenu,
49+
enabledContextMenus: computed(() => Object.entries(enabled) as [ContextMenuIds, boolean][]),
50+
};
51+
});
52+
53+
export const useContextMenuStoreRefs = () => toRefs(useContextMenuStore());

src/utils/browser/browser-context.utils.ts

+5
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@
22
* Browser context alias
33
*/
44
export const context: typeof chrome.contextMenus | undefined = globalThis?.chrome?.contextMenus;
5+
6+
/**
7+
* Browser context menu clicked event alias
8+
*/
9+
export type ContextMenuOnClickedData = chrome.contextMenus.OnClickData;

src/web/init-services.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useMovieStore } from '~/stores/data/movie.store';
88
import { useSearchStore } from '~/stores/data/search.store';
99
import { useShowStore } from '~/stores/data/show.store';
1010
import { useAuthSettingsStore } from '~/stores/settings/auth.store';
11+
import { useContextMenuStore } from '~/stores/settings/context-menu.store';
1112
import { useExtensionSettingsStore } from '~/stores/settings/extension.store';
1213
import { useLinksStore } from '~/stores/settings/links.store';
1314
import { useLogStore } from '~/stores/settings/log.store';
@@ -48,6 +49,7 @@ export const initServices = async () => {
4849
useSearchStore().initSearchStore(),
4950
useShowStore().initShowStore(),
5051
useMovieStore().initMovieStore(),
52+
useContextMenuStore().initContextMenuStore(),
5153
]);
5254

5355
setAppReady(true);

0 commit comments

Comments
 (0)