Skip to content

Commit c3f19f7

Browse files
committed
Add support for multiple variants keyed on same hw model
1 parent 7e7d79d commit c3f19f7

File tree

3 files changed

+105
-28
lines changed

3 files changed

+105
-28
lines changed

components/Device.vue

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,23 +156,23 @@
156156
</h2>
157157
</div>
158158
<div
159-
v-for="device in store.sortedDevices.filter(d => isSupporterDevice(d) && d.supportLevel != 3)"
159+
v-for="device in uniqueDevices.filter(d => isSupporterDevice(d) && d.supportLevel != 3)"
160160
class="w-full sm:w-auto sm:max-w-sm border hover:border-gray-300 border-gray-600 rounded-lg m-1 sm:m-2 cursor-pointer hover:scale-105 shadow hover:shadow-[0_35px_60px_-15px_rgba(200,200,200,.3)]"
161161
@click="setSelectedTarget(device)"
162162
>
163163
<DeviceDetail :device="device" />
164164
</div>
165165
<hr class="w-full border-gray-400 my-4">
166166
<div
167-
v-if="store.sortedDevices.filter(d => !isSupporterDevice(d) || d.supportLevel == 3).length > 0"
167+
v-if="uniqueDevices.filter(d => !isSupporterDevice(d) || d.supportLevel == 3).length > 0"
168168
class="w-full text-center"
169169
>
170170
<h2 class="text-lg sm:text-xl text-yellow-400">
171171
{{ $t('device.diy_devices') }}
172172
</h2>
173173
</div>
174174
<div
175-
v-for="device in store.sortedDevices.filter(d => !isSupporterDevice(d) || d.supportLevel == 3)"
175+
v-for="device in uniqueDevices.filter(d => !isSupporterDevice(d) || d.supportLevel == 3)"
176176
class="w-full sm:w-auto sm:max-w-sm border hover:border-gray-300 border-gray-600 rounded-lg m-1 sm:m-2 cursor-pointer hover:scale-105 shadow hover:shadow-2xl"
177177
@click="setSelectedTarget(device)"
178178
>
@@ -184,7 +184,7 @@
184184
class="p-1 sm:p-2 m-1 sm:m-2 flex flex-wrap items-center justify-center"
185185
>
186186
<div
187-
v-for="device in store.sortedDevices"
187+
v-for="device in uniqueDevices"
188188
class="w-full sm:w-auto sm:max-w-sm border hover:border-gray-300 border-gray-600 rounded-lg m-1 sm:m-2 cursor-pointer hover:scale-105 hover:shadow-2xl"
189189
@click="store.setSelectedTarget(device)"
190190
>
@@ -213,13 +213,26 @@ import { useDeviceStore } from '../stores/deviceStore'
213213
import { useFirmwareStore } from '../stores/firmwareStore'
214214
import DeviceDetail from './DeviceDetail.vue'
215215
import { useI18n } from 'vue-i18n'
216+
import { computed } from 'vue'
216217
217218
const { t } = useI18n()
218219
219220
const store = useDeviceStore()
220221
const firmwareStore = useFirmwareStore()
221222
store.fetchList()
222223
224+
const uniqueDevices = computed(() => {
225+
const seen = new Set<string>()
226+
return store.sortedDevices.filter((device) => {
227+
const groupKey = device.key || `${device.hwModel}-${device.displayName}`
228+
if (seen.has(groupKey)) {
229+
return false
230+
}
231+
seen.add(groupKey)
232+
return true
233+
})
234+
})
235+
223236
const isSupporterDevice = (device: DeviceHardware) => {
224237
return device.tags?.some((tag: string) => supportedVendorDeviceTags.includes(tag))
225238
}

components/targets/Uf2.vue

Lines changed: 85 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,43 @@
9494
</ol>
9595

9696
<div v-if="firmwareStore.canShowFlash">
97-
<a
98-
v-if="firmwareStore.selectedFirmware?.id"
99-
:href="downloadUf2FileUrl"
100-
class="text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
101-
>
102-
{{ $t('flash.uf2.download_uf2') }}
103-
</a>
104-
<button
105-
v-else
106-
class="text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
107-
@click="downloadUf2FileFs"
108-
>
109-
{{ $t('flash.uf2.download_uf2') }}
110-
</button>
97+
<template v-if="hasVariantChoices">
98+
<div class="grid gap-2">
99+
<template v-for="variant in variantTargets" :key="variant.platformioTarget">
100+
<a
101+
v-if="firmwareStore.selectedFirmware?.id"
102+
:href="getDownloadUf2Url(variant)"
103+
class="text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
104+
>
105+
{{ formatVariantLabel(variant) }} &ndash; {{ $t('flash.uf2.download_uf2') }}
106+
</a>
107+
<button
108+
v-else
109+
type="button"
110+
class="text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
111+
@click="downloadUf2FileFsForTarget(variant)"
112+
>
113+
{{ formatVariantLabel(variant) }} &ndash; {{ $t('flash.uf2.download_uf2') }}
114+
</button>
115+
</template>
116+
</div>
117+
</template>
118+
<template v-else>
119+
<a
120+
v-if="firmwareStore.selectedFirmware?.id"
121+
:href="getDownloadUf2Url(deviceStore.$state.selectedTarget)"
122+
class="text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
123+
>
124+
{{ $t('flash.uf2.download_uf2') }}
125+
</a>
126+
<button
127+
v-else
128+
class="text-black inline-flex w-full justify-center bg-meshtastic hover:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
129+
@click="downloadUf2FileFsForTarget(deviceStore.$state.selectedTarget)"
130+
>
131+
{{ $t('flash.uf2.download_uf2') }}
132+
</button>
133+
</template>
111134
</div>
112135
</div>
113136
</div>
@@ -119,24 +142,63 @@ import {
119142
FolderDown,
120143
Info,
121144
} from 'lucide-vue-next'
122-
import { track } from '@vercel/analytics'
123145
import { computed } from 'vue'
124146
125147
import { useDeviceStore } from '../../stores/deviceStore'
126148
import { useFirmwareStore } from '../../stores/firmwareStore'
149+
import type { DeviceHardware } from '~/types/api'
127150
import FlashHeader from './FlashHeader.vue'
128151
import ReleaseNotes from './ReleaseNotes.vue'
129152
130153
const deviceStore = useDeviceStore()
131154
const firmwareStore = useFirmwareStore()
132155
133-
const downloadUf2FileFs = () => {
156+
const variantTargets = computed<DeviceHardware[]>(() => {
157+
const selected = deviceStore.$state.selectedTarget
158+
if (!selected) return []
159+
const targets = deviceStore.targets || []
160+
let candidates = targets.filter(target => target.hwModel === selected.hwModel)
161+
if (selected.key) {
162+
const keyMatches = targets.filter(target => target.key === selected.key)
163+
if (keyMatches.length > 0) {
164+
candidates = keyMatches
165+
}
166+
}
167+
168+
const uniqueByEnv = new Map<string, DeviceHardware>()
169+
candidates.forEach((candidate) => {
170+
uniqueByEnv.set(candidate.platformioTarget, candidate)
171+
})
172+
173+
const result = Array.from(uniqueByEnv.values())
174+
if (!result.find(candidate => candidate.platformioTarget === selected.platformioTarget)) {
175+
result.push(selected)
176+
}
177+
178+
return result.sort((a, b) => {
179+
if (a.displayName === b.displayName) {
180+
return (a.variant || '').localeCompare(b.variant || '')
181+
}
182+
return a.displayName.localeCompare(b.displayName)
183+
})
184+
})
185+
186+
const hasVariantChoices = computed(() => variantTargets.value.length > 1)
187+
188+
const formatVariantLabel = (target?: DeviceHardware) => {
189+
if (!target) return ''
190+
return target.variant ? `${target.displayName} ${target.variant}` : target.displayName
191+
}
192+
193+
const downloadUf2FileFsForTarget = (target?: DeviceHardware) => {
194+
if (!target) return
134195
let suffix = ''
135196
if (firmwareStore.shouldInstallInkHud) {
136197
suffix = '-inkhud'
137198
}
138-
const searchRegex = new RegExp(`firmware-${deviceStore.$state.selectedTarget.platformioTarget}${suffix}-.+.uf2`)
199+
const searchRegex = new RegExp(`firmware-${target.platformioTarget}${suffix}-.+.uf2`)
139200
console.log(searchRegex)
201+
firmwareStore.trackDownload(target, false)
140202
firmwareStore.downloadUf2FileSystem(searchRegex)
141203
}
142204
@@ -148,19 +210,19 @@ const isNewFirmware = computed(() => {
148210
const canInstallInkHud = computed(() => {
149211
if (!isNewFirmware.value)
150212
return false
151-
return deviceStore.$state.selectedTarget.hasInkHud === true
213+
return deviceStore.$state.selectedTarget?.hasInkHud === true
152214
})
153215
154-
const downloadUf2FileUrl = computed(() => {
155-
if (!firmwareStore.selectedFirmware?.id) return ''
216+
const getDownloadUf2Url = (target?: DeviceHardware) => {
217+
if (!target || !firmwareStore.selectedFirmware?.id) return ''
156218
const firmwareVersion = firmwareStore.selectedFirmware.id.replace('v', '')
157219
let suffix = ''
158220
if (firmwareStore.shouldInstallInkHud) {
159221
suffix = '-inkhud'
160222
}
161-
const firmwareFile = `firmware-${deviceStore.$state.selectedTarget.platformioTarget}${suffix}-${firmwareVersion}.uf2`
162-
firmwareStore.trackDownload(deviceStore.$state.selectedTarget, false)
223+
const firmwareFile = `firmware-${target.platformioTarget}${suffix}-${firmwareVersion}.uf2`
224+
firmwareStore.trackDownload(target, false)
163225
console.log(firmwareFile)
164226
return firmwareStore.getReleaseFileUrl(firmwareFile)
165-
})
227+
}
166228
</script>

types/api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ export interface DeviceHardware {
2828
requiresDfu?: boolean
2929
hasMui?: boolean
3030
hasInkHud?: boolean
31-
url?: string
31+
url?: string,
32+
key?: string; // Optional key to differentiate multiple entries for the same hwModel
33+
variant?: string; // Optional variant to differentiate multiple entries for the same hwModel
3234
}
3335

3436
export function getCorsFriendyReleaseUrl(url: string) {

0 commit comments

Comments
 (0)