Skip to content

Commit

Permalink
feat: amateur radio satellite name and norad id search
Browse files Browse the repository at this point in the history
  • Loading branch information
HoshinoSuzumi committed Apr 8, 2024
1 parent 581ed56 commit a382da9
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 53 deletions.
56 changes: 56 additions & 0 deletions app/satellites/TransponderCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {Transponder} from '@/app/satellites/page'
import {Icon} from '@iconify-icon/react'
import {rubik} from '@/app/fonts'

export const TransponderCard = ({
transponder,
}: {
transponder: Transponder
}) => {
return (
<div
className={`w-full px-3 py-2 bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded ${rubik.className}`}>
<div className={'flex flex-col gap-2'}>
<div className={'flex items-center gap-0.5 text-nowrap'}>
<Icon
title={transponder.status}
icon={transponder.status === 'active' ? 'tabler:circle-check-filled' : transponder.status === 'inactive' ? 'tabler:circle-x-filled' : 'tabler:help-circle-filled'}
className={`text-xl -ml-1 ${transponder.status === 'active' ? 'text-green-500' : transponder.status === 'inactive' ? 'text-red-500' : 'text-gray-500'}`}
/>
<span
title={transponder.mode}
className={'font-medium text-ellipsis overflow-hidden'}
>
{transponder.mode || 'UNKNOWN MODE'}
</span>
</div>
{transponder.callsign || transponder.beacon && (
<div className={`flex justify-between items-center flex-wrap`}>
{transponder.callsign && (
<div className={'flex items-center gap-1'}>
<Icon icon={'tabler:id'} className={'text-base text-primary'}/>
<span title={'呼号'}>{transponder.callsign}</span>
</div>
)}
{transponder.beacon && (
<div className={'flex items-center gap-1'}>
<Icon icon={'tabler:radar-2'} className={'text-base text-primary'}/>
<span title={'信标'}>{transponder.beacon}</span>
</div>
)}
</div>
)}
<div className={`flex justify-between items-center flex-wrap`}>
<div className={'flex items-center gap-1'}>
<Icon icon={'tabler:antenna'} className={'text-base text-primary'}/>
<span title={'上行频率'}>{transponder.uplink || '--'}</span>
</div>
<div className={'flex items-center gap-1'}>
<Icon icon={'tabler:satellite'} className={'text-base text-primary'}/>
<span title={'下行频率'}>{transponder.downlink || '--'}</span>
</div>
</div>
</div>
</div>
)
}
216 changes: 165 additions & 51 deletions app/satellites/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,73 +7,166 @@ import {Input, Table} from '@douyinfe/semi-ui'
import {IconSearch} from '@douyinfe/semi-icons'
import useSWR from 'swr'
import {ColumnProps} from '@douyinfe/semi-ui/lib/es/table'
import {Key, useRef, useState} from 'react'
import {TransponderCard} from '@/app/satellites/TransponderCard'

export interface FrequenciesRawData {
status: string
name: string
norad_id: number
satnogs_id: string
uplink: string
downlink: string
beacon: string
mode: string
}

export interface Transponder {
uplink?: string
downlink?: string
beacon?: string
mode?: string
callsign?: string
status: string
}

export interface FrequenciesData {
name: string
norad_id: number
satnogs_id: string
transponders: Transponder[]
}

export default function Page() {
const columns: ColumnProps<{
status: string
name: string
mode: string
}>[] = [
{
title: '状态',
dataIndex: 'status',
},
{
title: '卫星名称',
dataIndex: 'name',
},
{
title: 'NORAD ID',
dataIndex: 'norad_id',
},
{
title: '上行',
dataIndex: 'uplink',
},
{
title: '下行',
dataIndex: 'downlink',
},
{
title: '信标',
dataIndex: 'beacon',
},
const compositionRef = useRef({isComposition: false})
const [filteredValue, setFilteredValue] = useState<FrequenciesData[]>([])

const columns: ColumnProps<FrequenciesData>[] = [
{
title: '模式',
dataIndex: 'mode',
title: '转发器',
dataIndex: 'transponders',
width: 100,
align: 'center',
render: (record) => (
<div className={'mx-auto flex w-10 h-1.5 rounded-lg bg-black overflow-hidden divide-x dark:divide-neutral-700'}>
{record?.map((transponder: Transponder, index: Key | null | undefined) => (
<div
key={index}
style={{
width: 1 / record.length * 100 + '%',
}}
className={`h-full ${transponder.status === 'active' ? 'bg-green-500' : transponder.status === 'inactive' ? 'bg-red-500' : 'bg-gray-300'}`}
></div>
))}
</div>
),
filters: [
{
text: 'FM',
value: 'FM',
},
{
text: 'VHF/UHF',
value: 'VHF/UHF',
text: 'CW',
value: 'CW',
},
{
text: 'Linear',
value: 'Linear',
},
{
text: 'SSB',
value: 'SSB',
},
{
text: 'FSK',
value: 'FSK',
},
{
text: 'AFSK',
value: 'AFSK',
},
{
text: 'QPSK',
value: 'QPSK',
},
],
onFilter: (value, record) => {
console.log(value, record?.mode)
return record?.mode?.includes(value) || false
return record?.transponders.some(item => item.mode?.includes(value)) || false
},
},
{
title: '卫星名称',
dataIndex: 'name',
sorter: (a, b) => a?.name.localeCompare(b?.name || '') || 0,
onFilter: (value, record) => {
const lowerValue = value.toLowerCase()
return record?.name.toLowerCase().includes(lowerValue) || `${record?.norad_id || ''}`.includes(lowerValue) || false
},
filteredValue,
},
{
title: 'NORAD ID',
dataIndex: 'norad_id',
sorter: (a, b) => ((a?.norad_id || 0) - (b?.norad_id || 0)) || 0,
},
{
title: 'SatNOGS ID',
dataIndex: 'satnogs_id',
},
]

const {
data: frequenciesData,
isLoading: isFrequenciesLoading,
} = useSWR<[{
status: string
name: string
norad_id: string
uplink: string
downlink: string
beacon: string
mode: string
}]>('https://mirror.ghproxy.com/https://raw.githubusercontent.com/palewire/ham-satellite-database/main/data/amsat-all-frequencies.json')
} = useSWR<FrequenciesRawData[]>('https://mirror.ghproxy.com/https://raw.githubusercontent.com/palewire/ham-satellite-database/main/data/amsat-all-frequencies.json')

const groupedFrequencies = frequenciesData?.reduce<FrequenciesData[]>((acc, cur) => {
const existing = acc.find(item => item.name === cur.name)
if (!existing) {
acc.push({
name: cur.name,
norad_id: cur.norad_id,
satnogs_id: cur.satnogs_id,
transponders: [
{
uplink: cur.uplink,
downlink: cur.downlink,
beacon: cur.beacon,
mode: cur.mode,
status: cur.status,
},
],
})
} else {
existing.transponders.push({
uplink: cur.uplink,
downlink: cur.downlink,
beacon: cur.beacon,
mode: cur.mode,
status: cur.status,
})
}
return acc
}, [])

function handleCompositionStart() {
compositionRef.current.isComposition = true
}

function handleCompositionEnd(event: any) {
compositionRef.current.isComposition = false
const value = event.target?.value
const newFilteredValue = value ? [value] : []
setFilteredValue(newFilteredValue)
}

// const groupedFrequencies = frequenciesData?.reduce((acc, cur) => {
// const existing = acc.
// }, [])
function handleChange(value: any) {
if (compositionRef.current.isComposition) {
return
}
const newFilteredValue = value ? [value] : []
setFilteredValue(newFilteredValue)
}

return (
<div className={'w-full h-full flex flex-col gap-8 items-center pt-8 md:p-8 bg-white dark:bg-neutral-900'}>
Expand All @@ -84,17 +177,38 @@ export default function Page() {
<span className={`text-xs opacity-50 ${rubik.className}`}>Amateur Radio Satellites Database</span>
</h1>
<Input
placeholder={'搜索卫星名称、国家、频率等'}
placeholder={'搜索卫星名称、NORAD ID'}
className={'!w-64 mt-4'}
size={'large'}
disabled={isFrequenciesLoading}
prefix={<IconSearch/>}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
onChange={handleChange}
/>
</div>
<div className={'flex-1 w-full md:w-[80%] md:border md:rounded-lg md:overflow-hidden'}>
<div className={'flex-1 w-full md:w-[80%]'}>
<Table
bordered
rowKey={'name'}
style={rubik.style}
columns={columns}
dataSource={frequenciesData}
pagination={false}
dataSource={groupedFrequencies}
expandCellFixed
hideExpandedColumn={false}
expandIcon={<Icon icon={'tabler:caret-right-filled'}/>}
expandedRowRender={(record) => (
<div className={'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4'}>
{record?.transponders.map((transponder, index) => (
<TransponderCard transponder={transponder} key={index}/>
))}
</div>
)}
pagination={{
pageSize: 30,
position: 'both',
formatPageText: (page) => `第 ${page?.currentStart}-${page?.currentEnd} 项,共 ${page?.total} 组`,
}}
loading={isFrequenciesLoading}
/>
</div>
Expand Down
8 changes: 6 additions & 2 deletions app/satellites/styles.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.semi-table-pagination-outer {
@apply px-4;
}
@apply px-4 flex-wrap items-center;
}

table {
@apply text-nowrap;
}

0 comments on commit a382da9

Please sign in to comment.