docs: update part list tables, move out of beta

This commit is contained in:
Prohurtz 2024-10-06 14:43:02 -05:00
parent 7ed813f90d
commit 5c92fecfc4
4 changed files with 237 additions and 304 deletions

View File

@ -46,7 +46,7 @@ const theme = {
text: "How to Build", text: "How to Build",
collapsible: true, collapsible: true,
items: [ items: [
{ text: "Parts List (BETA)", link: "/how_to_build/part_list" }, { text: "Parts List", link: "/how_to_build/part_list" },
{ {
text: "Preparing Cameras", text: "Preparing Cameras",
link: "/how_to_build/preparing_cameras" link: "/how_to_build/preparing_cameras"

View File

@ -12,15 +12,20 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="component in components" :key="component.name"> <tr v-for="(component, index) in components" :key="component.name">
<th class="component-col">{{ component.name }}</th> <th class="component-col">{{ component.name }}</th>
<td class="choice-col"> <td class="choice-col">
<div v-if="component.choices.length > 1" class="custom-select" @click="toggleDropdown(component)"> <div v-if="component.choices.length > 1" class="custom-select" @click="toggleDropdown(component, index)" :ref="`select-${component.name}`">
<div class="selected-option">{{ component.choices[component.selectedChoice].name }}</div> <div class="selected-option">
<div v-if="component.isOpen" class="options"> {{ component.choices[component.selectedChoice].name }}
<div v-for="(choice, index) in component.choices" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="dropdown-arrow" :class="{ 'rotate': component.isOpen }">
:key="index" <polyline points="6 9 12 15 18 9"></polyline>
@click="selectOption(component, index)" </svg>
</div>
<div v-if="component.isOpen" class="options" :class="{ 'options-above': isOptionsAbove(index) }">
<div v-for="(choice, choiceIndex) in component.choices"
:key="choiceIndex"
@click="selectOption(component, choiceIndex)"
class="option"> class="option">
{{ choice.name }} {{ choice.name }}
</div> </div>
@ -38,6 +43,7 @@
<p>Total: ${{ total.toFixed(2) }}</p> <p>Total: ${{ total.toFixed(2) }}</p>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
@ -218,8 +224,6 @@ export default {
selectedChoice: 0, selectedChoice: 0,
isOpen: false isOpen: false
}, },
// Add other components similarly
], ],
total: 0, total: 0,
}; };
@ -237,27 +241,20 @@ export default {
}); });
this.total = total; this.total = total;
}, },
toggleDropdown(component) { toggleDropdown(component, index) {
// Close all other dropdowns
this.components.forEach(comp => { this.components.forEach(comp => {
if (comp !== component) { if (comp !== component) {
comp.isOpen = false; comp.isOpen = false;
} }
}); });
// Toggle the clicked dropdown
component.isOpen = !component.isOpen; component.isOpen = !component.isOpen;
}, },
isOptionsAbove(component) { isOptionsAbove(index) {
if (this.$refs[`select-${component.name}`]) { return index >= this.components.length - 2;
const rect = this.$refs[`select-${component.name}`].getBoundingClientRect();
const spaceBelow = window.innerHeight - rect.bottom;
return spaceBelow < 200 && rect.top > 200;
}
return false;
}, },
selectOption(component, index) { selectOption(component, index) {
component.selectedChoice = index; component.selectedChoice = index;
component.isOpen = false; // This line closes the dropdown component.isOpen = false;
this.updatePrices(); this.updatePrices();
}, },
closeAllDropdowns() { closeAllDropdowns() {
@ -265,26 +262,21 @@ export default {
component.isOpen = false; component.isOpen = false;
}); });
}, },
// Add this new method
handleOutsideClick(event) { handleOutsideClick(event) {
if (!event.target.closest('.custom-select')) { if (!event.target.closest('.custom-select')) {
this.closeAllDropdowns(); this.closeAllDropdowns();
} }
} }
}, },
mounted() { mounted() {
this.updatePrices(); this.updatePrices();
document.addEventListener('click', this.handleOutsideClick); document.addEventListener('click', this.handleOutsideClick);
}, },
beforeUnmount() { beforeUnmount() {
document.removeEventListener('click', this.handleOutsideClick); document.removeEventListener('click', this.handleOutsideClick);
}, },
}; };
</script> </script>
<style scoped> <style scoped>
.table-container { .table-container {
width: 100%; width: 100%;
@ -312,34 +304,82 @@ th, td {
.cost-all-col { width: 13%; } .cost-all-col { width: 13%; }
.links-col { width: 30%; } .links-col { width: 30%; }
.multi-line-select { .custom-select {
position: relative;
width: 100%; width: 100%;
white-space: normal; cursor: pointer;
word-wrap: break-word; z-index: 1;
height: auto;
text-align: center;
} }
select { .custom-select:hover,
width: 100%; .custom-select:focus-within {
z-index: 2;
}
.selected-option {
border: 1px solid #999;
padding: 5px; padding: 5px;
box-sizing: border-box; background-color: #666;
white-space: normal; min-height: 30px;
height: auto; display: flex;
align-items: center;
justify-content: space-between;
text-align: center; text-align: center;
text-align-last: center; color: #fff;
transition: background-color 0.3s ease;
} }
/* This targets Webkit browsers like Chrome and Safari */ .selected-option:hover {
select option { background-color: #444;
white-space: normal;
word-wrap: break-word;
} }
/* This targets Firefox */ .options {
select:-moz-focusring { position: absolute;
color: transparent; left: 0;
text-shadow: 0 0 0 #000; right: 0;
background-color: #222;
border: 1px solid #444;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
top: 100%;
}
.options-above {
bottom: 100%;
top: auto;
}
.option {
padding: 5px;
border-bottom: 1px solid #444;
color: #fff;
background-color: #333;
}
.option:last-child {
border-bottom: none;
}
.option:hover {
background-color: #1e1e20;
}
.dropdown-arrow {
width: 16px;
height: 16px;
min-width: 16px;
min-height: 16px;
transition: transform 0.3s ease;
}
.dropdown-arrow.rotate {
transform: rotate(180deg);
}
.custom-select[aria-expanded="true"] .dropdown-arrow {
transform: rotate(180deg);
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@ -359,79 +399,4 @@ select:-moz-focusring {
width: auto; width: auto;
} }
} }
.custom-select {
position: relative;
width: 100%;
cursor: pointer;
z-index: 1;
}
.custom-select:hover,
.custom-select:focus-within {
z-index: 2;
}
.selected-option {
border: 1px solid #444;
padding: 5px;
background-color: #333;
min-height: 30px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
color: #545454;
}
.options {
position: absolute;
left: 0;
right: 0;
background-color: #222;
border: 1px solid #444;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
top: 100%;
}
.options-above {
bottom: 100%;
top: auto;
}
.option {
padding: 5px;
border-bottom: 1px solid #444;
color: #545454;
background-color: #333;
}
.option:last-child {
border-bottom: none;
}
.option:hover {
background-color: #1e1e20;
}
/* Ensure text is visible in all states */
.custom-select, .selected-option, .option {
color: #fff;
}
/* Add some contrast to the selected option */
.selected-option {
background-color: #1e1e20;
}
/* Improve visibility of options */
.options {
background-color: #222;
}
/* Add a subtle hover effect */
.option:hover {
background-color: #555;
}
</style> </style>

View File

@ -12,15 +12,20 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="component in components" :key="component.name"> <tr v-for="(component, index) in components" :key="component.name">
<th class="component-col">{{ component.name }}</th> <th class="component-col">{{ component.name }}</th>
<td class="choice-col"> <td class="choice-col">
<div v-if="component.choices.length > 1" class="custom-select" @click="toggleDropdown(component)"> <div v-if="component.choices.length > 1" class="custom-select" @click="toggleDropdown(component, index)" :ref="`select-${component.name}`">
<div class="selected-option">{{ component.choices[component.selectedChoice].name }}</div> <div class="selected-option">
<div v-if="component.isOpen" class="options"> {{ component.choices[component.selectedChoice].name }}
<div v-for="(choice, index) in component.choices" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="dropdown-arrow" :class="{ 'rotate': component.isOpen }">
:key="index" <polyline points="6 9 12 15 18 9"></polyline>
@click="selectOption(component, index)" </svg>
</div>
<div v-if="component.isOpen" class="options" :class="{ 'options-above': isOptionsAbove(index) }">
<div v-for="(choice, choiceIndex) in component.choices"
:key="choiceIndex"
@click="selectOption(component, choiceIndex)"
class="option"> class="option">
{{ choice.name }} {{ choice.name }}
</div> </div>
@ -38,12 +43,13 @@
<p>Total: ${{ total.toFixed(2) }}</p> <p>Total: ${{ total.toFixed(2) }}</p>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
tracker: 2, tracker: 2,
components: [ components: [
{ {
name: 'Type A USB Breakout', name: 'Type A USB Breakout',
choices: [ choices: [
@ -313,7 +319,6 @@ export default {
selectedChoice: 0, selectedChoice: 0,
isOpen: false isOpen: false
}, },
// Add other components similarly
], ],
total: 0, total: 0,
}; };
@ -331,27 +336,20 @@ export default {
}); });
this.total = total; this.total = total;
}, },
toggleDropdown(component) { toggleDropdown(component, index) {
// Close all other dropdowns
this.components.forEach(comp => { this.components.forEach(comp => {
if (comp !== component) { if (comp !== component) {
comp.isOpen = false; comp.isOpen = false;
} }
}); });
// Toggle the clicked dropdown
component.isOpen = !component.isOpen; component.isOpen = !component.isOpen;
}, },
isOptionsAbove(component) { isOptionsAbove(index) {
if (this.$refs[`select-${component.name}`]) { return index >= this.components.length - 2;
const rect = this.$refs[`select-${component.name}`].getBoundingClientRect();
const spaceBelow = window.innerHeight - rect.bottom;
return spaceBelow < 200 && rect.top > 200;
}
return false;
}, },
selectOption(component, index) { selectOption(component, index) {
component.selectedChoice = index; component.selectedChoice = index;
component.isOpen = false; // This line closes the dropdown component.isOpen = false;
this.updatePrices(); this.updatePrices();
}, },
closeAllDropdowns() { closeAllDropdowns() {
@ -359,26 +357,21 @@ export default {
component.isOpen = false; component.isOpen = false;
}); });
}, },
// Add this new method
handleOutsideClick(event) { handleOutsideClick(event) {
if (!event.target.closest('.custom-select')) { if (!event.target.closest('.custom-select')) {
this.closeAllDropdowns(); this.closeAllDropdowns();
} }
} }
}, },
mounted() { mounted() {
this.updatePrices(); this.updatePrices();
document.addEventListener('click', this.handleOutsideClick); document.addEventListener('click', this.handleOutsideClick);
}, },
beforeUnmount() { beforeUnmount() {
document.removeEventListener('click', this.handleOutsideClick); document.removeEventListener('click', this.handleOutsideClick);
}, },
}; };
</script> </script>
<style scoped> <style scoped>
.table-container { .table-container {
width: 100%; width: 100%;
@ -406,34 +399,82 @@ th, td {
.cost-all-col { width: 13%; } .cost-all-col { width: 13%; }
.links-col { width: 30%; } .links-col { width: 30%; }
.multi-line-select { .custom-select {
position: relative;
width: 100%; width: 100%;
white-space: normal; cursor: pointer;
word-wrap: break-word; z-index: 1;
height: auto;
text-align: center;
} }
select { .custom-select:hover,
width: 100%; .custom-select:focus-within {
z-index: 2;
}
.selected-option {
border: 1px solid #999;
padding: 5px; padding: 5px;
box-sizing: border-box; background-color: #666;
white-space: normal; min-height: 30px;
height: auto; display: flex;
align-items: center;
justify-content: space-between;
text-align: center; text-align: center;
text-align-last: center; color: #fff;
transition: background-color 0.3s ease;
} }
/* This targets Webkit browsers like Chrome and Safari */ .selected-option:hover {
select option { background-color: #444;
white-space: normal;
word-wrap: break-word;
} }
/* This targets Firefox */ .options {
select:-moz-focusring { position: absolute;
color: transparent; left: 0;
text-shadow: 0 0 0 #000; right: 0;
background-color: #222;
border: 1px solid #444;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
top: 100%;
}
.options-above {
bottom: 100%;
top: auto;
}
.option {
padding: 5px;
border-bottom: 1px solid #444;
color: #fff;
background-color: #333;
}
.option:last-child {
border-bottom: none;
}
.option:hover {
background-color: #1e1e20;
}
.dropdown-arrow {
width: 16px;
height: 16px;
min-width: 16px;
min-height: 16px;
transition: transform 0.3s ease;
}
.dropdown-arrow.rotate {
transform: rotate(180deg);
}
.custom-select[aria-expanded="true"] .dropdown-arrow {
transform: rotate(180deg);
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@ -453,79 +494,4 @@ select:-moz-focusring {
width: auto; width: auto;
} }
} }
.custom-select {
position: relative;
width: 100%;
cursor: pointer;
z-index: 1;
}
.custom-select:hover,
.custom-select:focus-within {
z-index: 2;
}
.selected-option {
border: 1px solid #444;
padding: 5px;
background-color: #333;
min-height: 30px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
color: #545454;
}
.options {
position: absolute;
left: 0;
right: 0;
background-color: #222;
border: 1px solid #444;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
top: 100%;
}
.options-above {
bottom: 100%;
top: auto;
}
.option {
padding: 5px;
border-bottom: 1px solid #444;
color: #545454;
background-color: #333;
}
.option:last-child {
border-bottom: none;
}
.option:hover {
background-color: #1e1e20;
}
/* Ensure text is visible in all states */
.custom-select, .selected-option, .option {
color: #fff;
}
/* Add some contrast to the selected option */
.selected-option {
background-color: #1e1e20;
}
/* Improve visibility of options */
.options {
background-color: #222;
}
/* Add a subtle hover effect */
.option:hover {
background-color: #555;
}
</style> </style>

View File

@ -24,28 +24,7 @@ There are two primary categories of trackers supported: wireless and wired.
It's super important that you really look at all your options and consider your use case before making a decision. It's super important that you really look at all your options and consider your use case before making a decision.
::: :::
## Option 1: Wireless over WiFi 2.4 GHz ## Option 1: Wired over USB Serial
### Wireless Capable ESPs:
* Seeed Studio XIAO Sense
* ESP32-CAM
* FREENOVE ESP32-S3-WROOM CAM Board
This offers a good starting point as the ESP32 CAM boards are cheaper than wired capable trackers.
However, they can be a bit more temperamental and if you are using a wired headset, they don't really make sense.
**Pros:**
- Not as experimental
- Less cables (power only)
- No issues with USB hubs
**Cons:**
- Requires two external antennas for optimal streaming quality
- Requires WiFi 2.4 GHz router or access point in reasonable proximity
- High risk of radio interference with FBT and other WiFi 2.4 GHz devices
- ESP32-CAM uses more power and heats up due to radio power needs
## Option 2: Wired over USB Serial (in beta)
### Wired Capable ESPs: ### Wired Capable ESPs:
* Seeed Studio XIAO Sense * Seeed Studio XIAO Sense
* FREENOVE ESP32-S3-WROOM CAM Board * FREENOVE ESP32-S3-WROOM CAM Board
@ -55,10 +34,33 @@ This is our latest setup recommended for users with wired headsets such as the V
**Pros:** **Pros:**
- Much better performance and higher framerate up to 70 FPS with lower latency - Much better performance and higher framerate up to 70 FPS with lower latency
- No conflicts with advanced FBT setups such as Vive or Tundra Trackers - No conflicts with advanced FBT setups such as Vive or Tundra Trackers
- Can be wireless or wired - Can be wireless *or* wired
- Less soldering required - Less soldering required compared to ESP32-CAM boards
**Cons:** **Cons:**
- Requires USB port i.e. on Valve Index and USB hub mounted on your headset - Requires USB port i.e. on Valve Index and USB hub mounted on your headset
- Can be slightly more costly - Can be slightly more costly
- When in use with a Vive Facial Tracker, an MTT USB hub is required - When in use with a Vive Facial Tracker or other bandwidth sensitive components, a MTT USB hub is required
- Requires beta app versions until the v2.0 app is released
## Option 2: Wireless over WiFi 2.4 GHz
### Wireless Capable ESPs:
* Seeed Studio XIAO Sense
* ESP32-CAM
* FREENOVE ESP32-S3-WROOM CAM Board
This offers a good starting point as the ESP32-CAM boards are cheaper than wired capable trackers.
However, they can be a bit more temperamental and if you are using a wired headset, they don't really make sense.
**Pros:**
- Less cables (power only)
- No issues with USB hubs
- Easier to flash
- Less power draw
**Cons:**
- Requires two external antennas for optimal streaming quality
- Requires WiFi 2.4 GHz router or access point in reasonable proximity
- High risk of radio interference with FBT and other WiFi 2.4 GHz devices
- Uses more power and heats up due to radio power needs