Merge remote-tracking branch 'soybeanjs/main' into dev
This commit is contained in:
commit
fb2bd4b227
@ -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();
|
||||
});
|
||||
|
@ -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';
|
||||
@ -18,7 +17,6 @@ defineOptions({
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const tabStore = useTabStore();
|
||||
|
||||
const bsWrapper = ref<HTMLElement>();
|
||||
@ -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() {
|
||||
|
@ -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 */
|
||||
@ -131,9 +140,26 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
|
||||
*/
|
||||
async function clearTabs(excludes: string[] = [], clearCache: boolean = false) {
|
||||
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);
|
||||
|
||||
if (clearCache) {
|
||||
@ -152,13 +178,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();
|
||||
|
Loading…
Reference in New Issue
Block a user