Merge pull request #207 from itdoginfo/fix/dashboard

fix: dashboard behavior on corner cases
This commit is contained in:
Kirill Sobakin
2025-10-07 23:41:06 +03:00
committed by GitHub
7 changed files with 124 additions and 43 deletions

View File

@@ -8,3 +8,4 @@ export * from './getProxyUrlName';
export * from './onMount';
export * from './getClashApiUrl';
export * from './splitProxyString';
export * from './preserveScrollForPage';

View File

@@ -0,0 +1,9 @@
export function preserveScrollForPage(renderFn: () => void) {
const scrollY = window.scrollY;
renderFn();
requestAnimationFrame(() => {
window.scrollTo({ top: scrollY });
});
}

View File

@@ -61,6 +61,12 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
(proxy) => proxy.code === `${section['.name']}-out`,
);
const parsedOutbound = JSON.parse(section.outbound_json);
const parsedTag = parsedOutbound?.tag
? decodeURIComponent(parsedOutbound?.tag)
: undefined;
const proxyDisplayName = parsedTag || outbound?.value?.name || '';
return {
withTagSelect: false,
code: outbound?.code || section['.name'],
@@ -68,10 +74,7 @@ export async function getDashboardSections(): Promise<IGetDashboardSectionsRespo
outbounds: [
{
code: outbound?.code || section['.name'],
displayName:
decodeURIComponent(JSON.parse(section.outbound_json)?.tag) ||
outbound?.value?.name ||
'',
displayName: proxyDisplayName,
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || '',
selected: true,

View File

@@ -3,7 +3,11 @@ import {
getPodkopStatus,
getSingboxStatus,
} from '../../methods';
import { getClashWsUrl, onMount } from '../../../helpers';
import {
getClashWsUrl,
onMount,
preserveScrollForPage,
} from '../../../helpers';
import {
triggerLatencyGroupTest,
triggerLatencyProxyTest,
@@ -31,6 +35,7 @@ async function fetchDashboardSections() {
store.set({
sectionsWidget: {
latencyFetching: false,
loading: false,
failed: !success,
data,
@@ -130,25 +135,41 @@ async function handleChooseOutbound(selector: string, tag: string) {
}
async function handleTestGroupLatency(tag: string) {
store.set({
sectionsWidget: {
...store.get().sectionsWidget,
latencyFetching: true,
},
});
await triggerLatencyGroupTest(tag);
await fetchDashboardSections();
store.set({
sectionsWidget: {
...store.get().sectionsWidget,
latencyFetching: false,
},
});
}
async function handleTestProxyLatency(tag: string) {
store.set({
sectionsWidget: {
...store.get().sectionsWidget,
latencyFetching: true,
},
});
await triggerLatencyProxyTest(tag);
await fetchDashboardSections();
}
function replaceTestLatencyButtonsWithSkeleton() {
document
.querySelectorAll('.dashboard-sections-grid-item-test-latency')
.forEach((el) => {
const newDiv = document.createElement('div');
newDiv.className = 'skeleton';
newDiv.style.width = '99px';
newDiv.style.height = '28px';
el.replaceWith(newDiv);
});
store.set({
sectionsWidget: {
...store.get().sectionsWidget,
latencyFetching: false,
},
});
}
// Renderer
@@ -170,8 +191,12 @@ async function renderSectionsWidget() {
},
onTestLatency: () => {},
onChooseOutbound: () => {},
latencyFetching: sectionsWidget.latencyFetching,
});
return preserveScrollForPage(() => {
container!.replaceChildren(renderedWidget);
});
return container!.replaceChildren(renderedWidget);
}
const renderedWidgets = sectionsWidget.data.map((section) =>
@@ -179,9 +204,8 @@ async function renderSectionsWidget() {
loading: sectionsWidget.loading,
failed: sectionsWidget.failed,
section,
latencyFetching: sectionsWidget.latencyFetching,
onTestLatency: (tag) => {
replaceTestLatencyButtonsWithSkeleton();
if (section.withTagSelect) {
return handleTestGroupLatency(tag);
}
@@ -194,7 +218,9 @@ async function renderSectionsWidget() {
}),
);
return container!.replaceChildren(...renderedWidgets);
return preserveScrollForPage(() => {
container!.replaceChildren(...renderedWidgets);
});
}
async function renderBandwidthWidget() {

View File

@@ -6,6 +6,7 @@ interface IRenderSectionsProps {
section: Podkop.OutboundGroup;
onTestLatency: (tag: string) => void;
onChooseOutbound: (selector: string, tag: string) => void;
latencyFetching: boolean;
}
function renderFailedState() {
@@ -31,6 +32,7 @@ export function renderDefaultState({
section,
onChooseOutbound,
onTestLatency,
latencyFetching,
}: IRenderSectionsProps) {
function testLatency() {
if (section.withTagSelect) {
@@ -95,14 +97,16 @@ export function renderDefaultState({
},
section.displayName,
),
E(
'button',
{
class: 'btn dashboard-sections-grid-item-test-latency',
click: () => testLatency(),
},
_('Test latency'),
),
latencyFetching
? E('div', { class: 'skeleton', style: 'width: 99px; height: 28px' })
: E(
'button',
{
class: 'btn dashboard-sections-grid-item-test-latency',
click: () => testLatency(),
},
_('Test latency'),
),
]),
E(
'div',

View File

@@ -141,6 +141,7 @@ export interface StoreType {
loading: boolean;
failed: boolean;
data: Podkop.OutboundGroup[];
latencyFetching: boolean;
};
}
@@ -172,6 +173,7 @@ const initialStore: StoreType = {
sectionsWidget: {
loading: true,
failed: false,
latencyFetching: false,
data: [],
},
};

View File

@@ -776,6 +776,15 @@ function splitProxyString(str) {
return str.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("//")).filter(Boolean);
}
// src/helpers/preserveScrollForPage.ts
function preserveScrollForPage(renderFn) {
const scrollY = window.scrollY;
renderFn();
requestAnimationFrame(() => {
window.scrollTo({ top: scrollY });
});
}
// src/clash/methods/createBaseApiRequest.ts
async function createBaseApiRequest(fetchFn) {
try {
@@ -925,6 +934,9 @@ async function getDashboardSections() {
const outbound = proxies.find(
(proxy) => proxy.code === `${section[".name"]}-out`
);
const parsedOutbound = JSON.parse(section.outbound_json);
const parsedTag = parsedOutbound?.tag ? decodeURIComponent(parsedOutbound?.tag) : void 0;
const proxyDisplayName = parsedTag || outbound?.value?.name || "";
return {
withTagSelect: false,
code: outbound?.code || section[".name"],
@@ -932,7 +944,7 @@ async function getDashboardSections() {
outbounds: [
{
code: outbound?.code || section[".name"],
displayName: decodeURIComponent(JSON.parse(section.outbound_json)?.tag) || outbound?.value?.name || "",
displayName: proxyDisplayName,
latency: outbound?.value?.history?.[0]?.delay || 0,
type: outbound?.value?.type || "",
selected: true
@@ -1207,6 +1219,7 @@ var initialStore = {
sectionsWidget: {
loading: true,
failed: false,
latencyFetching: false,
data: []
}
};
@@ -1245,7 +1258,8 @@ function renderLoadingState() {
function renderDefaultState({
section,
onChooseOutbound,
onTestLatency
onTestLatency,
latencyFetching
}) {
function testLatency() {
if (section.withTagSelect) {
@@ -1301,7 +1315,7 @@ function renderDefaultState({
},
section.displayName
),
E(
latencyFetching ? E("div", { class: "skeleton", style: "width: 99px; height: 28px" }) : E(
"button",
{
class: "btn dashboard-sections-grid-item-test-latency",
@@ -1570,6 +1584,7 @@ async function fetchDashboardSections() {
const { data, success } = await getDashboardSections();
store.set({
sectionsWidget: {
latencyFetching: false,
loading: false,
failed: !success,
data
@@ -1659,20 +1674,35 @@ async function handleChooseOutbound(selector, tag) {
await fetchDashboardSections();
}
async function handleTestGroupLatency(tag) {
store.set({
sectionsWidget: {
...store.get().sectionsWidget,
latencyFetching: true
}
});
await triggerLatencyGroupTest(tag);
await fetchDashboardSections();
store.set({
sectionsWidget: {
...store.get().sectionsWidget,
latencyFetching: false
}
});
}
async function handleTestProxyLatency(tag) {
store.set({
sectionsWidget: {
...store.get().sectionsWidget,
latencyFetching: true
}
});
await triggerLatencyProxyTest(tag);
await fetchDashboardSections();
}
function replaceTestLatencyButtonsWithSkeleton() {
document.querySelectorAll(".dashboard-sections-grid-item-test-latency").forEach((el) => {
const newDiv = document.createElement("div");
newDiv.className = "skeleton";
newDiv.style.width = "99px";
newDiv.style.height = "28px";
el.replaceWith(newDiv);
store.set({
sectionsWidget: {
...store.get().sectionsWidget,
latencyFetching: false
}
});
}
async function renderSectionsWidget() {
@@ -1692,17 +1722,20 @@ async function renderSectionsWidget() {
onTestLatency: () => {
},
onChooseOutbound: () => {
}
},
latencyFetching: sectionsWidget.latencyFetching
});
return preserveScrollForPage(() => {
container.replaceChildren(renderedWidget);
});
return container.replaceChildren(renderedWidget);
}
const renderedWidgets = sectionsWidget.data.map(
(section) => renderSections({
loading: sectionsWidget.loading,
failed: sectionsWidget.failed,
section,
latencyFetching: sectionsWidget.latencyFetching,
onTestLatency: (tag) => {
replaceTestLatencyButtonsWithSkeleton();
if (section.withTagSelect) {
return handleTestGroupLatency(tag);
}
@@ -1713,7 +1746,9 @@ async function renderSectionsWidget() {
}
})
);
return container.replaceChildren(...renderedWidgets);
return preserveScrollForPage(() => {
container.replaceChildren(...renderedWidgets);
});
}
async function renderBandwidthWidget() {
console.log("renderBandwidthWidget");
@@ -1903,6 +1938,7 @@ return baseclass.extend({
maskIP,
onMount,
parseValueList,
preserveScrollForPage,
renderDashboard,
splitProxyString,
triggerLatencyGroupTest,