Skip to content

Commit d4100b5

Browse files
authored
Merge pull request #10 from mstephen19/develop
Develop
2 parents 1e30800 + 96eb23b commit d4100b5

File tree

15 files changed

+153
-74
lines changed

15 files changed

+153
-74
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ Tab Samurai offers four primary features.
4646

4747
## License
4848

49-
Tab Samurai is an open-source project operating under a modified MIT license. [See license](./LICENSE)
49+
Tab Samurai is an open-source project operating under a modified MIT license. [See license](./LICENSE).

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifest_version": 3,
33
"name": "Tab Samurai",
44
"short_name": "Tabs",
5-
"version": "1.1.1",
5+
"version": "1.2.0",
66
"description": "Take your browsing experience to the next level with automatic tab hibernation, smart tab management, and intuitive tab recovery.",
77
"icons": {
88
"16": "public/icon16.png",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "tabs",
33
"private": true,
4-
"version": "1.1.1",
4+
"version": "1.2.0",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src/background/cache.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
import { store, storageMemCache } from '../storage';
1+
import { store, storageMemCache, collections, collectionMemCache } from '../storage';
22

33
export const config = storageMemCache(store.config);
4+
export const tabPageStates = collectionMemCache(collections.tabPageStates);

src/background/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as tabState from './tabState';
22
import * as tabDiscard from './tabDiscard';
33
import * as tabMetadata from './tabMetadata';
44
import * as cache from './cache';
5+
import * as updates from './updates';
56
import { defaultConfig, UNINSTALL_URL } from '../consts';
67
import './listener/index';
78

@@ -12,11 +13,13 @@ async function main() {
1213
// Tab discard is dependent on tabState & user config (Settings)
1314
tabState.initialize(),
1415
cache.config.init(defaultConfig),
16+
cache.tabPageStates.init(),
1517
]);
1618

1719
tabDiscard.initialize();
1820

1921
chrome.runtime.setUninstallURL(UNINSTALL_URL);
22+
updates.initialize();
2023
}
2124

2225
main();

src/background/tabDiscard.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { collections } from '../storage';
2-
import { config } from './cache';
2+
import * as cache from './cache';
33
import { store } from '../storage';
44
import { tabs as tabUtils, logger } from '../utils';
55

@@ -56,14 +56,14 @@ export const initialize = async () => {
5656

5757
// Initial scheduling
5858
Object.entries(timestamps).forEach(([tabId, timestamp]) => {
59-
if (tabUtils.shouldWhitelist(tabs.find(({ id }) => id === +tabId)!, config.latest!)) {
59+
if (tabUtils.shouldWhitelist(tabs.find(({ id }) => id === +tabId)!, cache.config.latest!, cache.tabPageStates.latest)) {
6060
tabWhitelist.add(+tabId);
6161
return;
6262
}
6363

6464
if (tabs.find(({ id }) => id === +tabId)?.discarded) return;
6565

66-
scheduler.schedule(tabId, timestamp, config.latest!.discardTabsAfterMilliseconds);
66+
scheduler.schedule(tabId, timestamp, cache.config.latest!.discardTabsAfterMilliseconds);
6767
});
6868

6969
log.debug('Initialized');
@@ -78,7 +78,7 @@ export const initialize = async () => {
7878
return;
7979
}
8080

81-
scheduler.schedule(tabId, timestamp, config.latest!.discardTabsAfterMilliseconds);
81+
scheduler.schedule(tabId, timestamp, cache.config.latest!.discardTabsAfterMilliseconds);
8282
};
8383

8484
collections.tabDeactivationTimestamps.onChange(handleTabActivationChange);
@@ -90,7 +90,7 @@ export const initialize = async () => {
9090
const tab = await chrome.tabs.get(tabId);
9191

9292
// If a change was made to the tab that means it should now be whitelisted, add to the whitelist
93-
if (tabUtils.shouldWhitelist(tab, config.latest!)) {
93+
if (tabUtils.shouldWhitelist(tab, cache.config.latest!, cache.tabPageStates.latest)) {
9494
scheduler.unschedule(tabId);
9595
tabWhitelist.add(tabId);
9696
return;
@@ -99,12 +99,39 @@ export const initialize = async () => {
9999
// If the tab was previously marked as should never discard, but that's now changed,
100100
// schedule the discard
101101
if (tabWhitelist.has(tabId)) {
102+
// Remove whitelist status
103+
tabWhitelist.delete(tabId);
104+
102105
const timestamp = await collections.tabDeactivationTimestamps.get(tabId);
103106

104-
scheduler.schedule(tabId, timestamp, config.latest!.discardTabsAfterMilliseconds);
107+
scheduler.schedule(tabId, timestamp, cache.config.latest!.discardTabsAfterMilliseconds);
108+
}
109+
});
110+
111+
cache.tabPageStates.onChange(async (tabId, state, latest) => {
112+
// Tab should be whitelisted if it's recording audio/video
113+
if (state && (state.audio || state.video)) {
114+
scheduler.unschedule(tabId);
115+
tabWhitelist.add(+tabId);
116+
return;
105117
}
106118

107-
tabWhitelist.delete(tabId);
119+
// Otherwise, shouldn't be in whitelist
120+
121+
// If it is in the whitelist already though
122+
if (tabWhitelist.has(+tabId)) {
123+
const tab = await chrome.tabs.get(+tabId);
124+
125+
// Check if other conditions say the tab should remain whitelisted, don't remove it
126+
if (tabUtils.shouldWhitelist(tab, cache.config.latest!, latest)) return;
127+
128+
// Otherwise, remove it
129+
tabWhitelist.delete(+tabId);
130+
131+
const timestamp = await collections.tabDeactivationTimestamps.get(tabId);
132+
133+
scheduler.schedule(tabId, timestamp, cache.config.latest!.discardTabsAfterMilliseconds);
134+
}
108135
});
109136

110137
store.config.onChange(async (latest, previous) => {
@@ -123,7 +150,7 @@ export const initialize = async () => {
123150
if (tab.discarded || !tab.id) return;
124151

125152
const tabAlreadyInWhitelist = tabWhitelist.has(tab.id);
126-
const shouldAddToWhitelist = tabUtils.shouldWhitelist(tab, latest);
153+
const shouldAddToWhitelist = tabUtils.shouldWhitelist(tab, latest, cache.tabPageStates.latest);
127154

128155
// If the tab should be added to the whitelist, but is already in it, do nothing
129156
// Improves performance by not iterating through every tab each time

src/background/updates.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { store } from '../storage';
2+
3+
export const initialize = async () => {
4+
const [availableUpdateVersion, updateDetails] = await Promise.all([
5+
store.availableUpdateVersion.read(),
6+
chrome.runtime.requestUpdateCheck(),
7+
]);
8+
9+
const updateAvailable = updateDetails.status === 'update_available';
10+
// const throttled = updateDetails.status === 'throttled';
11+
12+
// If an update is available & not already tracked
13+
if (updateAvailable && updateDetails.version !== availableUpdateVersion) {
14+
console.log('hi');
15+
store.availableUpdateVersion.write(updateDetails.version);
16+
}
17+
18+
// If an update isn't available but a version is still tracked
19+
if (!updateAvailable && availableUpdateVersion) {
20+
store.availableUpdateVersion.write(null);
21+
}
22+
23+
chrome.runtime.onUpdateAvailable.addListener(({ version }) => {
24+
store.availableUpdateVersion.write(version);
25+
});
26+
};

src/popup/Accordions/QuickActions/index.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { useCallback, useContext, useMemo } from 'react';
22
import { TabsContext } from '../../context/TabsProvider';
3-
import { Box, Button, type ButtonProps, styled } from '@mui/material';
3+
import { Box, Button, type ButtonProps, styled, Tooltip } from '@mui/material';
44
import { ConfigContext } from '../../context/ConfigProvider';
55
import { pluralize, tabs as tabUtils } from '../../../utils';
66
import { toast } from '../../Toast';
7+
import { PageStateContext } from '../../context/PageStateProvider';
78

89
const QuickActionButton = styled((props: ButtonProps) => <Button {...props} variant='contained' />)({
910
textTransform: 'none',
@@ -13,12 +14,15 @@ const QuickActionButton = styled((props: ButtonProps) => <Button {...props} vari
1314
export const QuickActions = () => {
1415
const tabs = useContext(TabsContext);
1516
const config = useContext(ConfigContext);
17+
const pageStates = useContext(PageStateContext);
1618

17-
const allTabsDiscardedActiveOrWhitelisted = tabs.every((tab) => tab.discarded || tab.active || tabUtils.shouldWhitelist(tab, config));
19+
const allTabsDiscardedActiveOrWhitelisted = tabs.every(
20+
(tab) => tab.discarded || tab.active || tabUtils.shouldWhitelist(tab, config, pageStates)
21+
);
1822

1923
const handleSuspendInactiveTabs = useCallback(async () => {
2024
try {
21-
const inactiveTabs = tabs.filter((tab) => !tab.active && !tab.discarded && !tabUtils.shouldWhitelist(tab, config));
25+
const inactiveTabs = tabs.filter((tab) => !tab.active && !tab.discarded && !tabUtils.shouldWhitelist(tab, config, pageStates));
2226

2327
await Promise.all(inactiveTabs.map((tab) => chrome.tabs.discard(tab.id!)));
2428

@@ -32,9 +36,9 @@ export const QuickActions = () => {
3236
message: 'Failed to hibernate inactive tabs.',
3337
});
3438
}
35-
}, [tabs, config]);
39+
}, [tabs, config, pageStates]);
3640

37-
const audibleTabs = tabs.filter((tab) => tab.audible);
41+
const audibleTabs = tabs.filter((tab) => tab.audible && !tab.mutedInfo?.muted);
3842
const someTabsAudible = Boolean(audibleTabs.length);
3943

4044
const handleMuteMediaTabs = useCallback(async () => {
@@ -93,17 +97,21 @@ export const QuickActions = () => {
9397

9498
return (
9599
<Box display='flex' gap='5px' flexWrap='wrap'>
96-
<QuickActionButton disabled={someDuplicateTabs} onClick={handleCloseDuplicateTabs}>
97-
Close Duplicate Tabs
98-
</QuickActionButton>
100+
<Tooltip title={`Found ${duplicateTabs.length} tabs which are duplicates`}>
101+
<QuickActionButton disabled={!someDuplicateTabs} onClick={handleCloseDuplicateTabs}>
102+
Close Duplicate Tabs
103+
</QuickActionButton>
104+
</Tooltip>
99105

100106
<QuickActionButton disabled={allTabsDiscardedActiveOrWhitelisted} onClick={handleSuspendInactiveTabs}>
101107
Hibernate All Inactive Tabs
102108
</QuickActionButton>
103109

104-
<QuickActionButton disabled={!someTabsAudible} onClick={handleMuteMediaTabs}>
105-
Mute All Media-Playing Tabs
106-
</QuickActionButton>
110+
<Tooltip title={`Found ${audibleTabs.length} audible tabs`}>
111+
<QuickActionButton disabled={!someTabsAudible} onClick={handleMuteMediaTabs}>
112+
Mute All Media-Playing Tabs
113+
</QuickActionButton>
114+
</Tooltip>
107115

108116
<QuickActionButton disabled={!someTabsUnpinned} onClick={handleCloseUnpinnedTabs}>
109117
Close All Unpinned Tabs

src/popup/Accordions/Settings/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const Settings = () => {
4343
<Box display='flex' flexDirection='column'>
4444
<ConfigName
4545
name='Hibernate Pinned Tabs'
46-
tip='Whether or not to hibernate pinned tabs. Tabs playing audio will never hibernate.'
46+
tip='Whether or not to hibernate pinned tabs. Tabs playing audio and/or recording video/audio will never hibernate.'
4747
/>
4848

4949
<FormControlLabel

src/popup/Accordions/TabManager/index.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export const TabManager = () => {
1616
const [inputText, setInputText] = useState('');
1717

1818
const groupedTabs = useMemo(() => {
19+
console.log(pageStates);
20+
1921
const groupedTabList = Object.entries(tabGroupFns[appData.manageTabsGroupBy](tabs));
2022

2123
switch (appData.manageTabsGroupBy) {
@@ -54,9 +56,10 @@ export const TabManager = () => {
5456
const pageStateA = pageStates[a.id!];
5557
const pageStateB = pageStates[b.id!];
5658

57-
if (pageStateA?.video !== pageStateB?.video) return pageStateA?.video ? -1 : 1;
58-
if (pageStateA?.audio !== pageStateB?.audio) return pageStateA?.audio ? -1 : 1;
59-
if (a.audible !== b.audible) return a.audible ? -1 : 1;
59+
if (Boolean(pageStateA?.video) !== Boolean(pageStateB?.video)) return pageStateA?.video ? -1 : 1;
60+
if (Boolean(pageStateA?.audio) !== Boolean(pageStateB?.audio)) return pageStateA?.audio ? -1 : 1;
61+
if (Boolean(a.audible && !a.mutedInfo?.muted) !== Boolean(b.audible && !b.mutedInfo?.muted))
62+
return a.audible && !a.mutedInfo?.muted ? -1 : 1;
6063
return 0;
6164
})
6265
);

0 commit comments

Comments
 (0)