Skip to content

Commit

Permalink
feat: add devices dialog in editor (#1765)
Browse files Browse the repository at this point in the history
  • Loading branch information
meteyou authored Feb 15, 2024
1 parent 88e9827 commit b9b793d
Show file tree
Hide file tree
Showing 15 changed files with 837 additions and 3 deletions.
13 changes: 11 additions & 2 deletions src/components/TheEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
:icon="isWriteable ? mdiFileDocumentEditOutline : mdiFileDocumentOutline"
:title="title">
<template #buttons>
<v-btn text tile class="d-none d-md-flex" @click="dialogDevices = true">
<v-icon small class="mr-1">{{ mdiUsb }}</v-icon>
{{ $t('Editor.DeviceDialog') }}
</v-btn>
<v-btn
v-if="restartServiceName === 'klipper'"
text
Expand Down Expand Up @@ -116,6 +120,7 @@
</v-card-actions>
</panel>
</v-dialog>
<devices-dialog :show-dialog="dialogDevices" @close="dialogDevices = false" />
</div>
</template>

Expand All @@ -135,14 +140,17 @@ import {
mdiHelp,
mdiHelpCircle,
mdiRestart,
mdiUsb,
} from '@mdi/js'
import type Codemirror from '@/components/inputs/Codemirror.vue'
import DevicesDialog from '@/components/dialogs/DevicesDialog.vue'
@Component({
components: { Panel, CodemirrorAsync },
components: { DevicesDialog, Panel, CodemirrorAsync },
})
export default class TheEditor extends Mixins(BaseMixin) {
private dialogConfirmChange = false
dialogConfirmChange = false
dialogDevices = false
formatFilesize = formatFilesize
Expand All @@ -157,6 +165,7 @@ export default class TheEditor extends Mixins(BaseMixin) {
mdiHelpCircle = mdiHelpCircle
mdiFileDocumentEditOutline = mdiFileDocumentEditOutline
mdiFileDocumentOutline = mdiFileDocumentOutline
mdiUsb = mdiUsb
private scrollbarOptions = { scrollbars: { autoHide: 'never' } }
Expand Down
111 changes: 111 additions & 0 deletions src/components/dialogs/DevicesDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<template>
<v-dialog :value="showDialog" width="500" persistent :fullscreen="isMobile">
<panel
id="devices-dialog"
:title="$t('DevicesDialog.Headline')"
:icon="mdiUsb"
card-class="devices-dialog"
:margin-bottom="false"
style="overflow: hidden"
:height="isMobile ? 0 : 548">
<template #buttons>
<v-menu :left="true" :offset-y="true" :close-on-content-click="false" attach="#devices-dialog">
<template #activator="{ on, attrs }">
<v-btn icon tile v-bind="attrs" v-on="on">
<v-icon small>{{ mdiCog }}</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item class="minHeight36">
<v-checkbox
v-model="hideSystemEntries"
class="mt-0"
hide-details
:label="$t('DevicesDialog.HideSystemEntries')" />
</v-list-item>
</v-list>
</v-menu>
<v-btn icon tile @click="closePrompt">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-tabs v-model="tab" fixed-tabs>
<v-tab v-for="tab in tabs" :key="tab.tab">{{ tab.title }}</v-tab>
</v-tabs>
<overlay-scrollbars style="max-height: 400px; overflow-x: hidden">
<v-tabs-items v-model="tab">
<v-tab-item v-for="canInterface in canInterfaces" :key="canInterface">
<devices-dialog-can :hide-system-entries="hideSystemEntries" :name="canInterface" />
</v-tab-item>
<v-tab-item key="serial">
<devices-dialog-serial :hide-system-entries="hideSystemEntries" />
</v-tab-item>
<v-tab-item key="usb">
<devices-dialog-usb :hide-system-entries="hideSystemEntries" />
</v-tab-item>
<v-tab-item key="video">
<devices-dialog-video :hide-system-entries="hideSystemEntries" />
</v-tab-item>
</v-tabs-items>
</overlay-scrollbars>
</panel>
</v-dialog>
</template>

<script lang="ts">
import { Component, Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import Panel from '@/components/ui/Panel.vue'
import { mdiCog, mdiCloseThick, mdiUsb } from '@mdi/js'
@Component({
components: { Panel },
})
export default class DevicesDialog extends Mixins(BaseMixin) {
mdiCog = mdiCog
mdiUsb = mdiUsb
mdiCloseThick = mdiCloseThick
tab = 'serial'
hideSystemEntries = true
@Prop({ type: Boolean, default: false }) showDialog!: boolean
get tabs() {
const output: { tab: string; title: string }[] = [
{
tab: 'serial',
title: 'Serial',
},
{
tab: 'usb',
title: 'USB',
},
{
tab: 'video',
title: 'Video',
},
]
this.canInterfaces.forEach((name) => {
output.push({
tab: name,
title: name.toUpperCase(),
})
})
return output.sort((a, b) => a.title.localeCompare(b.title))
}
get canInterfaces() {
return Object.keys(this.$store.state.server.system_info?.canbus ?? {})
}
closePrompt() {
this.$emit('close')
}
}
</script>

<style scoped></style>
84 changes: 84 additions & 0 deletions src/components/dialogs/DevicesDialogCan.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<template>
<overlay-scrollbars style="max-height: 400px; overflow-x: hidden">
<v-card-text>
<v-row>
<v-col class="text-center">
<v-btn :loading="loading" color="primary" @click="refresh">{{ $t('DevicesDialog.Refresh') }}</v-btn>
</v-col>
</v-row>
<v-row v-if="devices.length" class="mt-0">
<v-col>
<devices-dialog-can-device v-for="device in devices" :key="device.uuid" :device="device" />
</v-col>
</v-row>
<v-row v-else-if="loaded" class="mt-0">
<v-col class="col-8 mx-auto">
<p class="text-center text--disabled mb-0">{{ $t('DevicesDialog.NoDeviceFound') }}</p>
</v-col>
</v-row>
<v-row v-else class="mt-0">
<v-col class="col-8 mx-auto">
<p class="text-center text--disabled mb-0">{{ $t('DevicesDialog.ClickRefresh') }}</p>
</v-col>
</v-row>
<v-row v-if="devices.length === 0">
<v-col>
<v-alert dense outlined type="info" :icon="mdiInformationVariantCircle">
{{ $t('DevicesDialog.CanBusInfo') }}
<v-row class="my-0">
<v-col class="text-center">
<v-btn
href="https://docs.mainsail.xyz/overview/features/query-devices#can-devices"
color="info"
outlined
text
small>
open guide
</v-btn>
</v-col>
</v-row>
</v-alert>
</v-col>
</v-row>
</v-card-text>
</overlay-scrollbars>
</template>

<script lang="ts">
import { Component, Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import DevicesDialogCanDevice from '@/components/dialogs/DevicesDialogCanDevice.vue'
import { mdiInformationVariantCircle } from '@mdi/js'
export interface CanDevice {
application: string
uuid: string
}
@Component({
components: { DevicesDialogCanDevice },
})
export default class DevicesDialogCan extends Mixins(BaseMixin) {
mdiInformationVariantCircle = mdiInformationVariantCircle
devices: CanDevice[] = []
loading = false
loaded = false
@Prop({ type: String, required: true }) name!: string
@Prop({ type: Boolean, default: false }) hideSystemEntries!: boolean
async refresh() {
this.loading = true
this.devices = await fetch(`${this.apiUrl}/machine/peripherals/canbus?interface=${this.name}`)
.then((res) => res.json())
.then((res) => res.result.can_uuids ?? [])
this.loading = false
this.loaded = true
}
}
</script>

<style scoped></style>
32 changes: 32 additions & 0 deletions src/components/dialogs/DevicesDialogCanDevice.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<template>
<v-card outlined class="mt-3 w-100">
<v-card-text>
<v-row>
<v-col>
<div class="text-overline mb-2 d-flex flex-row">
<span>{{ device.application }}</span>
</div>
</v-col>
</v-row>
<v-row class="mt-0">
<v-col>
<textfield-with-copy label="UUID" :value="device.uuid" />
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>

<script lang="ts">
import { Component, Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { CanDevice } from '@/components/dialogs/DevicesDialogCan.vue'
import TextfieldWithCopy from '@/components/inputs/TextfieldWithCopy.vue'
@Component({
components: { TextfieldWithCopy },
})
export default class DevicesDialogCanDevice extends Mixins(BaseMixin) {
@Prop({ type: Object, required: true }) device!: CanDevice
}
</script>
72 changes: 72 additions & 0 deletions src/components/dialogs/DevicesDialogSerial.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<template>
<v-card-text>
<v-row>
<v-col class="text-center">
<v-btn :loading="loading" color="primary" @click="refresh">{{ $t('DevicesDialog.Refresh') }}</v-btn>
</v-col>
</v-row>
<v-row v-if="filteredDevices.length" class="mt-0">
<v-col>
<v-expansion-panels accordion>
<devices-dialog-serial-device
v-for="device in filteredDevices"
:key="device.path_by_hardware ?? device.device_path"
:device="device" />
</v-expansion-panels>
</v-col>
</v-row>
<v-row v-else-if="loaded" class="mt-0">
<v-col class="col-8 mx-auto">
<p class="text-center text--disabled mb-0">{{ $t('DevicesDialog.NoDeviceFound') }}</p>
</v-col>
</v-row>
<v-row v-else class="mt-0">
<v-col class="col-8 mx-auto">
<p class="text-center text--disabled mb-0">{{ $t('DevicesDialog.ClickRefresh') }}</p>
</v-col>
</v-row>
</v-card-text>
</template>

<script lang="ts">
import { Component, Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
export interface SerialDevice {
device_type: 'hardware_uart' | 'usb'
device_path: string
device_name: string
driver_name: string
path_by_hardware?: string
path_by_id?: string
usb_location?: string
}
@Component
export default class DevicesDialogSerial extends Mixins(BaseMixin) {
devices: SerialDevice[] = []
loading = false
loaded = false
@Prop({ type: Boolean, default: false }) hideSystemEntries!: boolean
get filteredDevices() {
if (!this.hideSystemEntries) return this.devices
return this.devices.filter((device) => device.device_type !== 'hardware_uart')
}
async refresh() {
this.loading = true
this.devices = await fetch(this.apiUrl + '/machine/peripherals/serial')
.then((res) => res.json())
.then((res) => res.result?.serial_devices ?? [])
this.loading = false
this.loaded = true
}
}
</script>

<style scoped></style>
45 changes: 45 additions & 0 deletions src/components/dialogs/DevicesDialogSerialDevice.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<template>
<v-card outlined class="mt-3 w-100">
<v-list-item three-line>
<v-list-item-content>
<div class="text-overline mb-2 d-flex flex-row">
<span>{{ device.device_type.toUpperCase().replaceAll('_', ' ') }}</span>
<v-spacer />
<span>{{ device.driver_name }}</span>
</div>
<v-list-item-title class="text-h5 mb-0">{{ device.device_name }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-card-text>
<v-row>
<v-col>
<textfield-with-copy :label="$t('DevicesDialog.DevicePath')" :value="device.device_path" />
</v-col>
</v-row>
<v-row v-if="device.path_by_id ?? false">
<v-col>
<textfield-with-copy :label="$t('DevicesDialog.PathById')" :value="device.path_by_id" />
</v-col>
</v-row>
<v-row v-if="device.path_by_hardware ?? false">
<v-col>
<textfield-with-copy :label="$t('DevicesDialog.PathByHardware')" :value="device.path_by_hardware" />
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>

<script lang="ts">
import { Component, Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { SerialDevice } from '@/components/dialogs/DevicesDialogSerial.vue'
import TextfieldWithCopy from '@/components/inputs/TextfieldWithCopy.vue'
@Component({
components: { TextfieldWithCopy },
})
export default class DevicesDialogSerialDevice extends Mixins(BaseMixin) {
@Prop({ type: Object, required: true }) device!: SerialDevice
}
</script>
Loading

0 comments on commit b9b793d

Please sign in to comment.