Skip to content

Commit

Permalink
Merge pull request jason5ng32#243 from jason5ng32/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
jason5ng32 authored Sep 27, 2024
2 parents 19ec907 + ab7b8c9 commit 7a6a05c
Show file tree
Hide file tree
Showing 24 changed files with 1,025 additions and 250 deletions.
36 changes: 35 additions & 1 deletion frontend/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<InfoMask :showMaskButton.value="showMaskButton" :infoMaskLevel.value="infoMaskLevel"
:toggleInfoMask="toggleInfoMask" />
<QueryIP ref="queryIPRef" />
<Shell ref="shellRef" />
<HelpModal ref="helpModalRef" />
<Footer ref="footerRef" />
<PWA />
Expand All @@ -37,6 +38,7 @@ import Footer from './components/Footer.vue';
// Widgets
import Preferences from './components/widgets/Preferences.vue';
import QueryIP from './components/widgets/QueryIP.vue';
import Shell from './components/widgets/Shell.vue';
import HelpModal from './components/widgets/Help.vue';
import PWA from './components/widgets/PWA.vue';
import Alert from './components/widgets/Toast.vue';
Expand All @@ -62,6 +64,7 @@ const configs = computed(() => store.configs);
const userPreferences = computed(() => store.userPreferences);
const shouldRefreshEveryThing = computed(() => store.shouldRefreshEveryThing);
const Status = computed(() => store.mountingStatus);
const openedCard = computed(() => store.currentPath.id);
// Template 里的 Ref
const navBarRef = ref(null);
Expand All @@ -75,6 +78,7 @@ const IPCheckRef = ref(null);
const connectivityRef = ref(null);
const webRTCRef = ref(null);
const dnsLeaksRef = ref(null);
const shellRef = ref(null);
// Data
Expand Down Expand Up @@ -404,14 +408,23 @@ const ShortcutKeys = (isOriginalSite) => {
description: t('shortcutKeys.DNSResolver'),
},
{
keys: "b",
keys: "C",
action: () => {
scrollToElement("AdvancedTools", 80);
advancedToolsRef.value.navigateAndToggleOffcanvas('/censorshipcheck');
trackEvent('Nav', 'NavClick', 'CensorshipCheck');
},
description: t('shortcutKeys.CensorshipCheck'),
},
{
keys: "b",
action: () => {
scrollToElement("AdvancedTools", 80);
advancedToolsRef.value.navigateAndToggleOffcanvas('/browserinfo');
trackEvent('Nav', 'NavClick', 'BrowserInfo');
},
description: t('shortcutKeys.BrowserInfo'),
},
{
keys: "W",
action: () => {
Expand All @@ -421,6 +434,19 @@ const ShortcutKeys = (isOriginalSite) => {
},
description: t('shortcutKeys.Whois'),
},
{
keys: "f",
action: () => {
if (openedCard !== 0) {
advancedToolsRef.value.fullScreen();
trackEvent('ShortCut', 'ShortCut', 'FullScreen');
}
else {
return
}
},
description: t('shortcutKeys.fullScreenAdvancedTools'),
},
{
keys: "m",
action: () => {
Expand Down Expand Up @@ -464,6 +490,14 @@ const ShortcutKeys = (isOriginalSite) => {
},
description: t('shortcutKeys.About'),
},
{
keys: "x",
action: () => {
shellRef.value.openModal();
trackEvent('ShortCut', 'ShortCut', 'Shell');
},
description: t('shortcutKeys.Shell'),
},
// help
{
keys: "?",
Expand Down
111 changes: 36 additions & 75 deletions frontend/components/Advanced.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
<p>{{ t('advancedtools.Note') }}</p>
</div>
<div class="row">
<div class="col-lg-3 col-md-6 col-12 mb-4" v-for="(card, index) in cards" :key="index">
<div class="col-lg-3 col-md-6 col-12 mb-4" v-for="(card, index) in cards.filter(card => card.enabled)"
:key="index">
<div class="jn-adv-card card jn-card" :class="{ 'dark-mode dark-mode-border': isDarkMode }">
<div class="card-body" @click.prevent="navigateAndToggleOffcanvas(card.path)" role="button">
<h3 :class="[isMobile ? 'mobile-h3' : 'fs-4']">
<h3 :class="[isMobile ? 'mobile-h3' : 'fs-4']" class="jn-adv-title">
<i class="bi bi-arrow-up-right-circle"></i> {{ t(card.titleKey) }}
</h3>
<p class="opacity-75">{{ t(card.noteKey) }}</p>
Expand All @@ -23,8 +24,7 @@
</div>
<div :data-bs-theme="isDarkMode ? 'dark' : ''" class="offcanvas offcanvas-bottom" tabindex="-1"
:class="[isMobile ? 'h-100' : '']" id="offcanvasTools" aria-labelledby="offcanvasToolsLabel">
<div class="offcanvas-header d-flex justify-content-end"
:class="[showTitle ? 'jn-offcanvas-header' : 'jn-offcanvas-header-noborder']">
<div class="offcanvas-header d-flex justify-content-end jn-offcanvas-header">
<button v-if="!isMobile" type="button" class="btn opacity-50 jn-bold" @click="fullScreen">
<span v-if="!isFullScreen">
<i class="bi bi-arrows-fullscreen"></i>
Expand All @@ -33,14 +33,13 @@
<i class="bi bi-fullscreen-exit"></i>
</span>
</button>
<Transition name="slide-fade">
<span v-if="showTitle" class="fw-medium"
:class="[isMobile ? 'mobile-h2 text-left' : 'fs-5 text-center ms-auto']">{{
cards[openedCard].icon }}
{{ t(cards[openedCard].titleKey) }}</span>
</Transition>

<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
<span v-if="openedCard >= 0" class="fw-medium"
:class="[isMobile ? 'mobile-h2 text-left' : 'fs-5 text-center ms-auto']">{{
cards[openedCard].icon }}
{{ t(cards[openedCard].titleKey) }}</span>

<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"
@click="resetNavigatorURL()"></button>
</div>
<div class="offcanvas-body pt-0" :class="[isMobile ? ' w-100' : 'jn-canvas-width']" ref="scrollContainer">
<router-view></router-view>
Expand Down Expand Up @@ -69,70 +68,26 @@ const scrollContainer = ref(null);
const router = useRouter();
const cards = reactive([
{ path: '/pingtest', icon: '⏱️', titleKey: 'pingtest.Title', noteKey: 'advancedtools.PingTestNote' },
{ path: '/mtrtest', icon: '📡', titleKey: 'mtrtest.Title', noteKey: 'advancedtools.MTRTestNote' },
{ path: '/ruletest', icon: '🚏', titleKey: 'ruletest.Title', noteKey: 'advancedtools.RuleTestNote' },
{ path: '/dnsresolver', icon: '🔦', titleKey: 'dnsresolver.Title', noteKey: 'advancedtools.DNSResolverNote' },
{ path: '/censorshipcheck', icon: '🚧', titleKey: 'censorshipcheck.Title', noteKey: 'advancedtools.CensorshipCheck' },
{ path: '/whois', icon: '📓', titleKey: 'whois.Title', noteKey: 'advancedtools.Whois' },
{ path: '/macchecker', icon: '🗄️', titleKey: 'macchecker.Title', noteKey: 'advancedtools.MacChecker' },
{ path: '/pingtest', icon: '⏱️', titleKey: 'pingtest.Title', noteKey: 'advancedtools.PingTestNote', enabled: true },
{ path: '/mtrtest', icon: '📡', titleKey: 'mtrtest.Title', noteKey: 'advancedtools.MTRTestNote', enabled: true },
{ path: '/ruletest', icon: '🚏', titleKey: 'ruletest.Title', noteKey: 'advancedtools.RuleTestNote', enabled: true },
{ path: '/dnsresolver', icon: '🔦', titleKey: 'dnsresolver.Title', noteKey: 'advancedtools.DNSResolverNote', enabled: true },
{ path: '/censorshipcheck', icon: '🚧', titleKey: 'censorshipcheck.Title', noteKey: 'advancedtools.CensorshipCheck', enabled: true },
{ path: '/whois', icon: '📓', titleKey: 'whois.Title', noteKey: 'advancedtools.Whois', enabled: true },
{ path: '/macchecker', icon: '🗄️', titleKey: 'macchecker.Title', noteKey: 'advancedtools.MacChecker', enabled: true },
{ path: '/browserinfo', icon: '🖥️', titleKey: 'browserinfo.Title', noteKey: 'advancedtools.BrowserInfo', enabled: true },
{ path: '/invisibilitytest', icon: '🫣', titleKey: 'invisibilitytest.Title', noteKey: 'advancedtools.InvisibilityTest', enabled: false }
]);
const cardInvisibilityTest = { path: '/invisibilitytest', icon: '🫣', titleKey: 'invisibilitytest.Title', noteKey: 'advancedtools.InvisibilityTest' };
const isFullScreen = ref(false);
const showTitle = ref(false);
const openedCard = ref(null);
// 控制标题显示
const handleScroll = () => {
const scrollTop = scrollContainer.value.scrollTop;
if (scrollTop > 60) {
showTitle.value = true;
} else {
showTitle.value = false;
}
};
const openedCard = computed(() => store.currentPath.id);
// 跳转到指定页面并打开
const navigateAndToggleOffcanvas = (routePath) => {
router.push(routePath);
switch (routePath) {
case '/pingtest':
trackEvent('Nav', 'NavClick', 'PingTest');
openedCard.value = 0;
break;
case '/mtrtest':
trackEvent('Nav', 'NavClick', 'MTRTest');
openedCard.value = 1;
break;
case '/ruletest':
trackEvent('Nav', 'NavClick', 'RuleTest');
openedCard.value = 2;
break;
case '/dnsresolver':
trackEvent('Nav', 'NavClick', 'DNSResolver');
openedCard.value = 3;
break;
case '/censorshipcheck':
trackEvent('Nav', 'NavClick', 'CensorshipCheck');
openedCard.value = 4;
break;
case '/whois':
trackEvent('Nav', 'NavClick', 'Whois');
openedCard.value = 5;
break;
case '/macchecker':
trackEvent('Nav', 'NavClick', 'MacChecker');
openedCard.value = 6;
break;
case '/invisibilitytest':
trackEvent('Nav', 'NavClick', 'InvisibilityTest');
openedCard.value = 7;
break;
}
var offcanvas = new Offcanvas(document.getElementById('offcanvasTools'));
offcanvas.show();
let capitalizedRoutePath = routePath.replace('/', '');
capitalizedRoutePath = capitalizedRoutePath.charAt(0).toUpperCase() + capitalizedRoutePath.slice(1);
trackEvent('Nav', 'NavClick', capitalizedRoutePath);
};
// 全屏显示
Expand All @@ -153,21 +108,23 @@ const fullScreen = () => {
}
};
// 将浏览器地址重置
const resetNavigatorURL = () => {
router.push('/');
}
onMounted(() => {
store.setMountingStatus('advancedtools', true);
// 监听滚动事件
scrollContainer.value.addEventListener('scroll', handleScroll);
setTimeout(() => {
if (configs.value.originalSite) {
cards.push(cardInvisibilityTest);
cards.find(x => x.path === '/invisibilitytest').enabled = true;
}
}, 2000);
}, 1500);
});
defineExpose({
navigateAndToggleOffcanvas,
navigateAndToggleOffcanvas, fullScreen
});
</script>
Expand Down Expand Up @@ -235,6 +192,10 @@ defineExpose({
text-shadow: 0 0 10pt #00000060;
}
.jn-adv-title {
width: 85%;
}
.jn-offcanvas-header {
min-height: 40pt;
border-bottom: 1px solid #ababab3f;
Expand Down
72 changes: 36 additions & 36 deletions frontend/components/ConnectivityTest.vue
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
<template>
<div>
<!-- Connectivity -->
<div class="availability-test-section mb-4">
<div class="jn-title2">
<h2 id="Connectivity" :class="{ 'mobile-h2': isMobile }">🚦 {{ t('connectivity.Title') }}</h2>
<button @click="checkAllConnectivity(false, true, true)"
:class="['btn', isDarkMode ? 'btn-dark dark-mode-refresh' : 'btn-light']"
aria-label="Refresh Connectivity Test" v-tooltip="t('Tooltips.RefreshConnectivityTests')"><i class="bi"
:class="[isStarted ? 'bi-arrow-clockwise' : 'bi-caret-right-fill']"></i></button>
</div>
<div class="text-secondary">
<p>{{ t('connectivity.Note') }}</p>
</div>
<div class="row">
<div v-for="test in connectivityTests" :key="test.id" class="col-6 col-md-3 mb-4">
<div class="card jn-card keyboard-shortcut-card"
:class="{ 'dark-mode dark-mode-border': isDarkMode, 'jn-hover-card': !isMobile }">
<div class="card-body">
<p class="jn-con-title card-title" @click.prevent="checkConnectivityHandler(test, onTestComplete, true)" :title="t('connectivity.RefreshThisTest')"><i class="bi" :class="'bi-' + test.icon"></i> {{ test.name }}</p>
<p class="card-text" :class="{
<!-- Connectivity -->
<div class="availability-test-section mb-4">
<div class="jn-title2">
<h2 id="Connectivity" :class="{ 'mobile-h2': isMobile }">🚦 {{ t('connectivity.Title') }}</h2>
<button @click="checkAllConnectivity(false, true, true)"
:class="['btn', isDarkMode ? 'btn-dark dark-mode-refresh' : 'btn-light']" aria-label="Refresh Connectivity Test"
v-tooltip="t('Tooltips.RefreshConnectivityTests')"><i class="bi"
:class="[isStarted ? 'bi-arrow-clockwise' : 'bi-caret-right-fill']"></i></button>
</div>
<div class="text-secondary">
<p>{{ t('connectivity.Note') }}</p>
</div>
<div class="row">
<div v-for="test in connectivityTests" :key="test.id" class="col-6 col-md-3 mb-4">
<div class="card jn-card keyboard-shortcut-card"
:class="{ 'dark-mode dark-mode-border': isDarkMode, 'jn-hover-card': !isMobile }">
<div class="card-body">
<p class="jn-con-title card-title" @click.prevent="checkConnectivityHandler(test, onTestComplete, true)"
:title="t('connectivity.RefreshThisTest')"><i class="bi" :class="'bi-' + test.icon"></i> {{ test.name }}
</p>
<p class="card-text" :class="{
'text-info': test.status === t('connectivity.StatusWait'),
'text-success': test.status.includes(t('connectivity.StatusAvailable')) && test.time < 200,
'jn-text-warning': test.status.includes(t('connectivity.StatusAvailable')) && test.time >= 200,
'text-danger': test.status === t('connectivity.StatusUnavailable') || test.status === t('connectivity.StatusTimeout')
}" :title="t('connectivity.minTestTime') + test.mintime + ' ms'">
<i v-if="test.status === t('connectivity.StatusUnavailable') || test.status === t('connectivity.StatusTimeout')"
class="bi bi-emoji-frown"></i>
<i v-else-if="test.status === t('connectivity.StatusAvailable') && test.time < 200"
class="bi bi-emoji-smile"></i>
<i v-else-if="test.status === t('connectivity.StatusAvailable') && test.time >= 200"
class="bi bi-emoji-expressionless"></i>
<i v-else-if="test.time === 0" class="bi bi-hourglass-split"></i>
{{ test.status }}
<span v-if="test.time !== 0">
: {{ test.time }}
<span> ms</span>
</span>
</p>
</div>
<i v-if="test.status === t('connectivity.StatusUnavailable') || test.status === t('connectivity.StatusTimeout')"
class="bi bi-emoji-frown"></i>
<i v-else-if="test.status === t('connectivity.StatusAvailable') && test.time < 200"
class="bi bi-emoji-smile"></i>
<i v-else-if="test.status === t('connectivity.StatusAvailable') && test.time >= 200"
class="bi bi-emoji-expressionless"></i>
<i v-else-if="test.time === 0" class="bi bi-hourglass-split"></i>
{{ test.status }}
<span v-if="test.time !== 0">
: {{ test.time }}
<span> ms</span>
</span>
</p>
</div>
</div>
</div>
Expand Down Expand Up @@ -147,13 +147,13 @@ const connectivityTests = reactive([
]);
// 检查网络连通性
const checkConnectivityHandler = (test, onTestComplete = () => {}, isManualRun) => {
const checkConnectivityHandler = (test, onTestComplete = () => { }, isManualRun) => {
const beginTime = +new Date();
manualRun.value = isManualRun;
let img = new Image();
let timeout = setTimeout(() => {
test.status = t('connectivity.StatusUnavailable');
onTestComplete(false);
onTestComplete(false);
}, 3 * 1200);
img.onload = () => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/components/IpInfos.vue
Original file line number Diff line number Diff line change
Expand Up @@ -295,14 +295,14 @@ let ipDataCache = new Map();
// 公共获取 IP 地址方法
const fetchIP = async (cardID, getFromSource) => {
const { ip, source } = await getFromSource();
const { ip, source } = await getFromSource(configs.value.originalSite);
let fetchingStatus = false;
if (ip !== null) {
ipDataCards[cardID].ip = ip;
ipDataCards[cardID].source = source;
IPArray.value = [...IPArray.value, ip];
await fetchIPDetails(cardID, ip);
} else if (cardID === 3 || cardID === 5) {
} else if (cardID === 2 || cardID === 5) {
ipDataCards[cardID].ip = t('ipInfos.IPv6Error');
} else {
ipDataCards[cardID].ip = t('ipInfos.IPv4Error');
Expand Down
Loading

0 comments on commit 7a6a05c

Please sign in to comment.