Skip to content

Commit

Permalink
feat: two-columns groups for mobile inspired by Clash Dash and Surge
Browse files Browse the repository at this point in the history
  • Loading branch information
Zephyruso committed Feb 23, 2025
1 parent 0d8db22 commit 4d435ab
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 9 deletions.
137 changes: 137 additions & 0 deletions src/components/proxies/ProxyGroupForMobile.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<template>
<div
class="relative h-20"
ref="cardRef"
@click="handlerGroupClick"
>
<div
v-if="activeMode"
class="fixed inset-0 z-40 bg-black/50"
></div>
<div
class="card absolute h-auto w-full origin-top-left transform bg-base-100 transition-all duration-200"
:class="[
activeMode ? 'z-50 max-h-[70vh]' : '',
isLeft ? 'left-0' : 'right-0',
isTop ? 'top-0' : 'bottom-0',
]"
:style="[activeMode && '--tw-bg-opacity: 1;width: calc(100vw - 1rem)']"
@contextmenu.prevent.stop="handlerLatencyTest"
>
<div class="flex h-20 shrink-0 flex-col gap-1 p-2">
<ProxyIcon
v-if="proxyGroup.icon"
:icon="proxyGroup.icon"
size="small"
class="absolute right-2 top-2 h-10 !w-10"
:class="!activeMode && 'opacity-60'"
/>
<div class="text-md truncate">
{{ proxyGroup.name }}
</div>
<div class="truncate text-xs text-base-content/80">
{{ proxyGroup.now }}
</div>

<div class="flex h-4 justify-between gap-1">
<span class="text-xs text-base-content/60">
{{ proxyGroup.type }} ({{ proxiesCount }})
</span>
<button
v-if="manageHiddenGroup"
class="btn btn-circle btn-xs z-10 ml-1"
@click.stop="handlerGroupToggle"
>
<EyeIcon
v-if="!hiddenGroupMap[proxyGroup.name]"
class="h-3 w-3"
/>
<EyeSlashIcon
v-else
class="h-3 w-3"
/>
</button>
<LatencyTag
:class="twMerge('z-10 bg-base-200/40 hover:shadow')"
:loading="isLatencyTesting"
:name="proxyGroup.now"
:group-name="proxyGroup.name"
@click.stop="handlerLatencyTest"
/>
</div>
</div>

<div
v-if="activeMode"
class="grid flex-1 grid-cols-2 gap-2 overflow-y-auto overflow-x-hidden p-2"
>
<ProxyNodeCard
v-for="node in renderProxies"
:key="node"
:name="node"
:group-name="proxyGroup.name"
:active="node === proxyGroup.now"
@click="handlerProxySelect(node)"
/>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { useRenderProxies } from '@/composables/renderProxies'
import { PROXY_TYPE } from '@/constant'
import { hiddenGroupMap, proxyGroupLatencyTest, proxyMap, selectProxy } from '@/store/proxies'
import { manageHiddenGroup } from '@/store/settings'
import { EyeIcon, EyeSlashIcon } from '@heroicons/vue/24/outline'
import { twMerge } from 'tailwind-merge'
import { computed, ref } from 'vue'
import LatencyTag from './LatencyTag.vue'
import ProxyIcon from './ProxyIcon.vue'
import ProxyNodeCard from './ProxyNodeCard.vue'
const props = defineProps<{
name: string
}>()
const proxyGroup = computed(() => proxyMap.value[props.name])
const allProxies = computed(() => proxyGroup.value.all ?? [])
const { proxiesCount, renderProxies } = useRenderProxies(allProxies, props.name)
const isLatencyTesting = ref(false)
const activeMode = ref(false)
const cardRef = ref()
const isLeft = ref(false)
const isTop = ref(false)
const handlerGroupClick = async () => {
if (!activeMode.value) {
const bounding = cardRef.value.getBoundingClientRect()
isLeft.value = bounding.left < window.innerWidth / 3
isTop.value = bounding.top < window.innerHeight / 2
}
activeMode.value = !activeMode.value
}
const handlerLatencyTest = async () => {
if (isLatencyTesting.value) return
isLatencyTesting.value = true
try {
await proxyGroupLatencyTest(props.name)
isLatencyTesting.value = false
} catch {
isLatencyTesting.value = false
}
}
const handlerGroupToggle = () => {
hiddenGroupMap.value[props.name] = !hiddenGroupMap.value[props.name]
}
const handlerProxySelect = (name: string) => {
if (proxyGroup.value.type.toLowerCase() === PROXY_TYPE.LoadBalance) return
selectProxy(props.name, name)
}
</script>
2 changes: 1 addition & 1 deletion src/components/settings/ProxiesSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
</div>
<div class="divider"></div>
<div class="grid grid-cols-1 gap-2 lg:grid-cols-2">
<div class="flex items-center gap-2 max-sm:hidden">
<div class="flex items-center gap-2">
{{ $t('twoColumnProxyGroup') }}
<input
class="toggle"
Expand Down
21 changes: 13 additions & 8 deletions src/views/ProxiesPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

<script setup lang="ts">
import ProxyGroup from '@/components/proxies/ProxyGroup.vue'
import ProxyGroupForMobile from '@/components/proxies/ProxyGroupForMobile.vue'
import ProxyProvider from '@/components/proxies/ProxyProvider.vue'
import { useProxies } from '@/composables/proxies'
import { PROXY_TAB_TYPE } from '@/constant'
Expand All @@ -42,16 +43,20 @@ import { computed } from 'vue'
const { proxiesTabShow, renderGroups } = useProxies()
const Comp = computed(() =>
proxiesTabShow.value === PROXY_TAB_TYPE.PROVIDER ? ProxyProvider : ProxyGroup,
)
const isLargeScreen = computed(() => {
return isSidebarCollapsed.value ? twoColumn.value : twoColumnWithSidebar.value
})
const Comp = computed(() => {
if (proxiesTabShow.value === PROXY_TAB_TYPE.PROVIDER) {
return ProxyProvider
}
return isLargeScreen.value ? ProxyGroup : ProxyGroupForMobile
})
const displayTwoColumns = computed(() => {
return (
(isSidebarCollapsed.value ? twoColumn.value : twoColumnWithSidebar.value) &&
twoColumnProxyGroup.value &&
renderGroups.value.length > 1
)
return twoColumnProxyGroup.value && renderGroups.value.length > 1
})
const filterContent: <T>(all: T[], target: number) => T[] = (all, target) => {
Expand Down

0 comments on commit 4d435ab

Please sign in to comment.