Skip to content
Merged
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
51 changes: 35 additions & 16 deletions packages/hooks/src/use-count-down.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,59 @@ import { computed, onScopeDispose, ref } from 'vue';
import { useRafFn } from '@vueuse/core';

/**
* count down
* A hook for implementing a countdown timer. It uses `requestAnimationFrame` for smooth and accurate timing,
* independent of the screen refresh rate.
*
* @param seconds - count down seconds
* @param initialSeconds - The total number of seconds for the countdown.
*/
export default function useCountDown(seconds: number) {
const FPS_PER_SECOND = 60;
export default function useCountDown(initialSeconds: number) {
const remainingSeconds = ref(0);

const fps = ref(0);
const count = computed(() => Math.ceil(remainingSeconds.value));

const count = computed(() => Math.ceil(fps.value / FPS_PER_SECOND));

const isCounting = computed(() => fps.value > 0);
const isCounting = computed(() => remainingSeconds.value > 0);

const { pause, resume } = useRafFn(
() => {
if (fps.value > 0) {
fps.value -= 1;
} else {
({ delta }) => {
// delta: milliseconds elapsed since the last frame.

// If countdown already reached zero or below, ensure it's 0 and stop.
if (remainingSeconds.value <= 0) {
remainingSeconds.value = 0;
pause();
return;
}

// Calculate seconds passed since the last frame.
const secondsPassed = delta / 1000;
remainingSeconds.value -= secondsPassed;

// If countdown has finished after decrementing.
if (remainingSeconds.value <= 0) {
remainingSeconds.value = 0;
pause();
}
},
{ immediate: false }
{ immediate: false } // The timer does not start automatically.
);

function start(updateSeconds: number = seconds) {
fps.value = FPS_PER_SECOND * updateSeconds;
/**
* Starts the countdown.
*
* @param [updatedSeconds=initialSeconds] - Optionally, start with a new duration. Default is `initialSeconds`
*/
function start(updatedSeconds: number = initialSeconds) {
remainingSeconds.value = updatedSeconds;
resume();
}

/** Stops the countdown and resets the remaining time to 0. */
function stop() {
fps.value = 0;
remainingSeconds.value = 0;
pause();
}

// Ensure the rAF loop is cleaned up when the component is unmounted.
onScopeDispose(() => {
pause();
});
Expand Down
10 changes: 2 additions & 8 deletions src/layouts/modules/global-tab/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useElementBounding } from '@vueuse/core';
import { PageTab } from '@sa/materials';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route';
import { useTabStore } from '@/store/modules/tab';
import { isPC } from '@/utils/agent';
import BetterScroll from '@/components/custom/better-scroll.vue';
Expand All @@ -18,7 +17,6 @@ defineOptions({
const route = useRoute();
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const tabStore = useTabStore();

const bsWrapper = ref<HTMLElement>();
Expand Down Expand Up @@ -82,12 +80,8 @@ function getContextMenuDisabledKeys(tabId: string) {
return disabledKeys;
}

async function handleCloseTab(tab: App.Global.Tab) {
await tabStore.removeTab(tab.id);

if (themeStore.resetCacheStrategy === 'close') {
routeStore.resetRouteCache(tab.routeKey);
}
function handleCloseTab(tab: App.Global.Tab) {
tabStore.removeTab(tab.id);
}

async function refresh() {
Expand Down
46 changes: 40 additions & 6 deletions src/store/modules/tab/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,22 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
const removeTabIndex = tabs.value.findIndex(tab => tab.id === tabId);
if (removeTabIndex === -1) return;

const removedTabRouteKey = tabs.value[removeTabIndex].routeKey;
const isRemoveActiveTab = activeTabId.value === tabId;
const nextTab = tabs.value[removeTabIndex + 1] || homeTab.value;

// remove tab
tabs.value.splice(removeTabIndex, 1);

// if current tab is removed, then switch to next tab
if (isRemoveActiveTab && nextTab) {
await switchRouteByTab(nextTab);
}

// reset route cache if cache strategy is close
if (themeStore.resetCacheStrategy === 'close') {
routeStore.resetRouteCache(removedTabRouteKey);
}
}

/** remove active tab */
Expand All @@ -131,9 +140,26 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
*/
async function clearTabs(excludes: string[] = []) {
const remainTabIds = [...getFixedTabIds(tabs.value), ...excludes];
const removedTabsIds = tabs.value.map(tab => tab.id).filter(id => !remainTabIds.includes(id));

// Identify tabs to be removed and collect their routeKeys if strategy is 'close'
const tabsToRemove = tabs.value.filter(tab => !remainTabIds.includes(tab.id));
const routeKeysToReset: RouteKey[] = [];

if (themeStore.resetCacheStrategy === 'close') {
for (const tab of tabsToRemove) {
routeKeysToReset.push(tab.routeKey);
}
}

const removedTabsIds = tabsToRemove.map(tab => tab.id);

// If no tabs are actually being removed based on excludes and fixed tabs, exit
if (removedTabsIds.length === 0) {
return;
}

const isRemoveActiveTab = removedTabsIds.includes(activeTabId.value);
// filterTabsByIds returns tabs NOT in removedTabsIds, so these are the tabs that will remain
const updatedTabs = filterTabsByIds(removedTabsIds, tabs.value);

function update() {
Expand All @@ -142,13 +168,21 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {

if (!isRemoveActiveTab) {
update();
return;
} else {
const activeTabCandidate = updatedTabs[updatedTabs.length - 1] || homeTab.value;

if (activeTabCandidate) {
// Ensure there's a tab to switch to
await switchRouteByTab(activeTabCandidate);
}
// Update the tabs array regardless of switch success or if a candidate was found
update();
}

const activeTab = updatedTabs[updatedTabs.length - 1] || homeTab.value;

await switchRouteByTab(activeTab);
update();
// After tabs are updated and route potentially switched, reset cache for removed tabs
for (const routeKey of routeKeysToReset) {
routeStore.resetRouteCache(routeKey);
}
}

const { routerPushByKey } = useRouterPush();
Expand Down