Skip to content

Commit 7ecb1fe

Browse files
committed
feat(side-panel): adds side-panel support
1 parent 4448b27 commit 7ecb1fe

12 files changed

+152
-7
lines changed

src/components/views/settings/SettingsComponent.vue

+4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ const SettingsActions = lazyComponent(
5555
const SettingsTheme = lazyComponent(
5656
() => import('~/components/views/settings/SettingsTheme.vue'),
5757
);
58+
const SettingsIconAction = lazyComponent(
59+
() => import('~/components/views/settings/SettingsIconAction.vue'),
60+
);
5861
5962
const i18n = useI18n('settings');
6063
@@ -83,6 +86,7 @@ const sections = computed<Section[]>(() =>
8386
{ title: 'menu__menus', reference: ref(), component: SettingsMenus },
8487
{ title: 'menu__watching', reference: ref(), component: SettingsWatching },
8588
{ title: 'menu__activity', reference: ref(), component: SettingsActivity },
89+
{ title: 'menu__icon', reference: ref(), component: SettingsIconAction },
8690
{ title: 'menu__badge', reference: ref(), component: SettingsBadge },
8791
{ title: 'menu__cache', reference: ref(), component: SettingsCache },
8892
{ title: 'menu__export', reference: ref(), component: SettingsExport },
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script lang="ts" setup>
2+
import { chromeRuntimeId } from '@dvcol/web-extension-utils/chrome/runtime';
3+
import { NSwitch } from 'naive-ui';
4+
5+
import { ref } from 'vue';
6+
7+
import SettingsFormItem from '~/components/views/settings/SettingsFormItem.vue';
8+
import { IconAction } from '~/models/icon-action';
9+
import { useExtensionSettingsStoreRefs } from '~/stores/settings/extension.store';
10+
import { useI18n } from '~/utils/i18n.utils';
11+
12+
const i18n = useI18n('settings', 'icon');
13+
14+
const { iconAction } = useExtensionSettingsStoreRefs();
15+
16+
const toggleIconAction = (isPanel = iconAction.value === IconAction.Panel) => {
17+
iconAction.value = isPanel ? IconAction.Popup : IconAction.Panel;
18+
};
19+
20+
const container = ref();
21+
</script>
22+
23+
<template>
24+
<div ref="container" class="icon-action-container">
25+
<!-- Enable -->
26+
<SettingsFormItem :label="i18n('label_toggle')">
27+
<NSwitch
28+
:value="iconAction === IconAction.Panel"
29+
class="form-switch"
30+
:disabled="!chromeRuntimeId"
31+
@update:value="() => toggleIconAction()"
32+
>
33+
<template #checked>{{ i18n('label_toggle_panel') }}</template>
34+
<template #unchecked>{{ i18n('label_toggle_popup') }}</template>
35+
</NSwitch>
36+
</SettingsFormItem>
37+
</div>
38+
</template>
39+
40+
<style lang="scss" scoped>
41+
.icon-action-container {
42+
display: flex;
43+
flex-direction: column;
44+
gap: 1.5rem;
45+
}
46+
47+
.form-select {
48+
min-width: 8rem;
49+
}
50+
51+
.form-switch {
52+
display: flex;
53+
flex: 1 1 auto;
54+
justify-content: center;
55+
min-width: 5rem;
56+
padding: 0 0.5rem;
57+
font-size: 0.75rem;
58+
}
59+
</style>
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"settings__icon__label_toggle": {
3+
"message": "Open Side Trakt in a side panel or a popup",
4+
"description": "Label for toggling between side panel and popup."
5+
},
6+
"settings__icon__label_toggle_panel": {
7+
"message": "Panel",
8+
"description": "Label for the side panel option."
9+
},
10+
"settings__icon__label_toggle_popup": {
11+
"message": "Popup",
12+
"description": "Label for the popup option."
13+
}
14+
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,9 @@
5454
"settings__menu__badge": {
5555
"message": "Badge",
5656
"description": "Badge ui menu item"
57+
},
58+
"settings__menu__icon": {
59+
"message": "Icon",
60+
"description": "Icon Action menu item"
5761
}
5862
}
+5-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
{
22
"settings__menus__label_menu__open_in_side_trakt": {
3-
"message": "Open selection in Side Trakt",
3+
"message": "Open selection in Side Trakt window",
44
"description": "Label to toggle the 'Open selection in Side Trakt' menu item"
55
},
66
"settings__menus__label_menu__add_to_search_history": {
77
"message": "Add selection to search history",
88
"description": "Label to toggle the 'Add selection to search history' menu item"
9+
},
10+
"settings__menus__label_menu__open_in_side_trakt_panel": {
11+
"message": "Open selection in Side Trakt side panel",
12+
"description": "Label to toggle the 'Open selection in Side Trakt side panel' menu item"
913
}
1014
}

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

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { openPopup } from '@dvcol/web-extension-utils/chrome/action';
22
import { context } from '@dvcol/web-extension-utils/chrome/context';
33
import { runtime } from '@dvcol/web-extension-utils/chrome/runtime';
4+
import { openPanel } from '@dvcol/web-extension-utils/chrome/side-panel';
45
import { createTab } from '@dvcol/web-extension-utils/chrome/tabs';
56

6-
import type { ContextMenuOnClickedData } from '@dvcol/web-extension-utils/chrome/models';
7+
import type { ContextMenuOnClickedData, Tab } from '@dvcol/web-extension-utils/chrome/models';
78
import type { RouteLocationNormalized } from 'vue-router';
89

910
import { ContextMenuConstants, ContextMenuId, type ContextMenuIds, ContextMenus } from '~/models/context/context-menu.model';
@@ -21,12 +22,20 @@ const setLastRoute = (data: ContextMenuOnClickedData) => {
2122
const openPopupApp = async () => {
2223
if (openPopup) return openPopup();
2324
if (!runtime) return;
25+
// Fallback to open options page
2426
await createTab({
2527
url: runtime.getURL('views/options/index.html'),
2628
});
2729
};
2830

29-
export const ContextMenusHooks: Record<ContextMenuIds, (data: ContextMenuOnClickedData) => void | Promise<void>> = {
31+
export const ContextMenusHooks: Record<ContextMenuIds, (data: ContextMenuOnClickedData, tab?: Tab) => void | Promise<void>> = {
32+
[ContextMenuId.OpenInSideTraktPanel]: async (data, tab) => {
33+
if (tab?.windowId && openPanel) await openPanel({ windowId: tab.windowId });
34+
await setLastRoute(data);
35+
if (tab?.windowId && openPanel) return;
36+
// Fallback to open popup/options page
37+
await openPopupApp();
38+
},
3039
[ContextMenuId.OpenInSideTrakt]: async data => {
3140
await setLastRoute(data);
3241
await openPopupApp();

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const ContextMenuId = {
22
OpenInSideTrakt: 'open_in_side_trakt',
33
AddToSearchHistory: 'add_to_search_history',
4+
OpenInSideTraktPanel: 'open_in_side_trakt_panel',
45
} as const;
56

67
export type ContextMenuIds = (typeof ContextMenuId)[keyof typeof ContextMenuId];
@@ -17,7 +18,12 @@ export type ContextMenu = {
1718
export const ContextMenus: Record<ContextMenuIds, ContextMenu> = {
1819
[ContextMenuId.OpenInSideTrakt]: {
1920
id: ContextMenuId.OpenInSideTrakt,
20-
title: 'Open in side trakt',
21+
title: 'Open Side Trakt window',
22+
contexts: ['selection'],
23+
},
24+
[ContextMenuId.OpenInSideTraktPanel]: {
25+
id: ContextMenuId.OpenInSideTraktPanel,
26+
title: 'Open Side Trakt panel',
2127
contexts: ['selection'],
2228
},
2329
[ContextMenuId.AddToSearchHistory]: {

src/models/icon-action.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const IconAction = {
2+
Popup: 'popup' as const,
3+
Panel: 'panel' as const,
4+
} as const;
5+
6+
export type IconActions = (typeof IconAction)[keyof typeof IconAction];

src/models/message/message-type.model.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { VersionUpdateDetails } from '@dvcol/web-extension-utils/chrome/models';
22
import type { ContextMenuIds } from '~/models/context/context-menu.model';
3+
import type { IconActions } from '~/models/icon-action';
34
import type { BadgeColorDetails, TabIconDetails } from '~/utils/browser/browser-message.utils';
45

56
export const MessageType = {
@@ -8,6 +9,7 @@ export const MessageType = {
89
AppReady: 'app-ready',
910
BadgeUpdate: 'badge-update',
1011
IconUpdate: 'icon-update',
12+
IconOptions: 'icon-options',
1113
} as const;
1214

1315
export type MessageTypes = (typeof MessageType)[keyof typeof MessageType];
@@ -32,4 +34,6 @@ export type MessagePayload<T extends MessageTypes = MessageTypes> = T extends ty
3234
? BadgeUpdatePayload
3335
: T extends typeof MessageType.IconUpdate
3436
? { icon?: TabIconDetails; color?: BadgeColorDetails }
35-
: never;
37+
: T extends typeof MessageType.IconOptions
38+
? { open: IconActions }
39+
: never;

src/scripts/background/index.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { setBadgeBackgroundColor, setBadgeText, setBadgeTextColor, setIcon, setTitle } from '@dvcol/web-extension-utils/chrome/action';
22
import { onContextMenuClicked } from '@dvcol/web-extension-utils/chrome/context';
33
import { onInstalledEvent, onVersionUpdate } from '@dvcol/web-extension-utils/chrome/runtime';
4+
import { getPanelBehavior, setPanelBehavior } from '@dvcol/web-extension-utils/chrome/side-panel';
45

56
import { ContextMenusHooks, installContextMenus } from '~/models/context/context-menu-hooks.model';
67
import { isContextMenuId } from '~/models/context/context-menu.model';
8+
import { IconAction } from '~/models/icon-action';
79
import { MessageType } from '~/models/message/message-type.model';
810
import { onMessage } from '~/utils/browser/browser-message.utils';
911
import { storage } from '~/utils/browser/browser-storage.utils';
@@ -40,9 +42,9 @@ try {
4042
}
4143

4244
try {
43-
onContextMenuClicked(async info => {
45+
onContextMenuClicked(async (info, tab) => {
4446
if (isContextMenuId(info.menuItemId)) {
45-
return ContextMenusHooks[info.menuItemId](info);
47+
return ContextMenusHooks[info.menuItemId](info, tab);
4648
}
4749
console.error('Unknown context menu event', info);
4850
});
@@ -73,3 +75,17 @@ try {
7375
} catch (error) {
7476
console.error('Failed to handle badge update message', error);
7577
}
78+
79+
try {
80+
onMessage(MessageType.IconOptions, async message => {
81+
if (!message.payload) return;
82+
if (!setPanelBehavior || !getPanelBehavior) return;
83+
const options = await getPanelBehavior();
84+
const openPanel = message.payload.open === IconAction.Panel;
85+
if (options.openPanelOnActionClick === openPanel) return;
86+
setPanelBehavior({ openPanelOnActionClick: openPanel });
87+
console.debug('App ready', { open: message.payload.open, options, openPanel });
88+
});
89+
} catch (error) {
90+
console.error('Failed to handle side panel options message', error);
91+
}

src/stores/settings/context-menu.store.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type ContextMenuState = Record<ContextMenuIds, boolean>;
1414
const defaultState = {
1515
[ContextMenuId.OpenInSideTrakt]: false,
1616
[ContextMenuId.AddToSearchHistory]: false,
17+
[ContextMenuId.OpenInSideTraktPanel]: false,
1718
} as const;
1819

1920
export const useContextMenuStore = defineStore(ContextMenuConstants.Store, () => {

src/stores/settings/extension.store.ts

+18
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import { defineStore, storeToRefs } from 'pinia';
55

66
import { computed, reactive, ref, toRaw } from 'vue';
77

8+
import type { IconActions } from '~/models/icon-action';
9+
810
import type { PosterItem } from '~/models/poster.model';
911

1012
import NewExtensionIcon from '~/assets/brand/favicon-128x128.png';
1113
import OldExtensionIcon from '~/assets/favicon/favicon-128x128.png';
14+
import { IconAction } from '~/models/icon-action';
1215
import { DefaultLists, type ListEntity } from '~/models/list.model';
1316
import { MessageType } from '~/models/message/message-type.model';
1417
import { NavbarPosition, type NavbarPositions } from '~/models/navbar-position.model';
@@ -118,6 +121,8 @@ export const DefaultQuickActionLists: QuickActionListDictionary = {
118121
[Route.Search]: DefaultLists.Watchlist,
119122
};
120123

124+
const sendIconActionUpdate = debounce((action: IconActions) => sendMessage({ type: MessageType.IconOptions, payload: { open: action } }), 500);
125+
121126
type ThemeColors = {
122127
main?: string;
123128
dark?: string;
@@ -144,6 +149,7 @@ type ExtensionSettings = {
144149
actionDate: ActionDateDictionary;
145150
actionLists: QuickActionListDictionary;
146151
theme: ThemeColors;
152+
iconAction: IconActions;
147153
};
148154

149155
const ExtensionSettingsConstants = {
@@ -175,6 +181,8 @@ export const useExtensionSettingsStore = defineStore(ExtensionSettingsConstants.
175181
const brand = ref<Brands>(Brand.Old);
176182
const theme = reactive<ThemeColors>({ background: 'transparent' });
177183

184+
const iconAction = ref<IconActions>(IconAction.Popup);
185+
178186
const clearState = () => {
179187
Object.assign(cacheRetention, DefaultCacheRetention);
180188
Object.assign(routeDictionary, DefaultRoutes);
@@ -207,6 +215,7 @@ export const useExtensionSettingsStore = defineStore(ExtensionSettingsConstants.
207215
actionDate: toRaw(actionDate),
208216
actionLists: toRaw(actionLists),
209217
theme: toRaw(theme),
218+
iconAction: iconAction.value,
210219
}),
211220
500,
212221
);
@@ -251,6 +260,7 @@ export const useExtensionSettingsStore = defineStore(ExtensionSettingsConstants.
251260
if (restored?.actionLists !== undefined) Object.assign(actionLists, restored.actionLists);
252261

253262
if (restored?.theme !== undefined) Object.assign(theme, restored.theme);
263+
if (restored?.iconAction !== undefined) iconAction.value = restored.iconAction;
254264

255265
if (!chromeRuntimeId) routeDictionary[Route.Progress] = false;
256266
};
@@ -449,6 +459,14 @@ export const useExtensionSettingsStore = defineStore(ExtensionSettingsConstants.
449459
saveState().catch(err => Logger.error('Failed to save icon only extension settings', { value, err }));
450460
},
451461
}),
462+
iconAction: computed<IconActions>({
463+
get: () => iconAction.value,
464+
set: (value: IconActions) => {
465+
iconAction.value = value;
466+
sendIconActionUpdate(value).catch(err => Logger.error('Failed to toggle icon action', { value, err }));
467+
saveState().catch(err => Logger.error('Failed to save icon action extension settings', { value, err }));
468+
},
469+
}),
452470
getImageSettings,
453471
setImageType,
454472
setImageFormat,

0 commit comments

Comments
 (0)