mirror of
https://github.com/lights0123/n-link.git
synced 2025-01-21 23:11:53 +00:00
Driver warnings
This commit is contained in:
parent
50a5496e31
commit
e60a0f5972
10
src-tauri/Cargo.lock
generated
10
src-tauri/Cargo.lock
generated
|
@ -21,6 +21,12 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0adac150c2dd5a9c864d054e07bda5e6bc010cd10036ea5f17e82a2f5867f735"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.13"
|
||||
|
@ -561,6 +567,9 @@ name = "hashbrown"
|
|||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
|
@ -841,6 +850,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"hashbrown",
|
||||
"indicatif",
|
||||
"lazy_static",
|
||||
"libnspire",
|
||||
|
|
|
@ -16,11 +16,12 @@ libnspire = "0.2.2"
|
|||
lazy_static = "1.4.0"
|
||||
rusb = "0.6.4"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
tauri = { version = "0.9", features = [ "event", "notification" ] }
|
||||
tauri = { version = "0.9", features = [ "event", "notification", "open" ] }
|
||||
native-dialog = "0.4.3"
|
||||
clap = "3.0.0-beta.2"
|
||||
indicatif = "0.15.0"
|
||||
libusb1-sys = { version = "0.4.2", features = [ "vendored" ] }
|
||||
hashbrown = "0.9.0"
|
||||
|
||||
[target."cfg(windows)".build-dependencies]
|
||||
winres = "0.1"
|
||||
|
|
|
@ -2,10 +2,12 @@ use std::sync::Arc;
|
|||
use std::time::Duration;
|
||||
|
||||
use libnspire::{PID, PID_CX2, VID};
|
||||
use rusb::{GlobalContext, Error};
|
||||
use rusb::{DeviceHandle, Error, GlobalContext};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Device, DeviceState};
|
||||
use tauri::WebviewMut;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Promise {
|
||||
|
@ -150,32 +152,93 @@ pub fn add_device(dev: Arc<rusb::Device<GlobalContext>>) -> rusb::Result<((u8, u
|
|||
if !(descriptor.vendor_id() == VID && matches!(descriptor.product_id(), PID | PID_CX2)) {
|
||||
return Err(rusb::Error::Other);
|
||||
}
|
||||
let handle = dev.open()?;
|
||||
|
||||
let (name, needs_drivers) = match dev.open() {
|
||||
Ok(handle) => (
|
||||
handle.read_product_string(
|
||||
handle.read_languages(Duration::from_millis(100))?[0],
|
||||
&descriptor,
|
||||
Duration::from_millis(100),
|
||||
)?,
|
||||
false,
|
||||
),
|
||||
Err(rusb::Error::NotSupported) => (
|
||||
if descriptor.product_id() == PID_CX2 {
|
||||
"TI Nspire CX II"
|
||||
} else {
|
||||
"TI Nspire"
|
||||
}
|
||||
.to_string(),
|
||||
true,
|
||||
),
|
||||
Err(other) => return Err(other),
|
||||
};
|
||||
|
||||
Ok((
|
||||
(dev.bus_number(), dev.address()),
|
||||
Device {
|
||||
name: dbg!(handle.read_product_string(
|
||||
handle.read_languages(Duration::from_millis(100))?[0],
|
||||
&descriptor,
|
||||
Duration::from_millis(100),
|
||||
))?,
|
||||
name,
|
||||
device: dev,
|
||||
state: DeviceState::Closed,
|
||||
needs_drivers,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn enumerate() -> Result<(), libnspire::Error> {
|
||||
crate::DEVICES.write().unwrap().extend(
|
||||
rusb::devices()?
|
||||
.iter()
|
||||
.filter_map(|dev| match add_device(Arc::new(dev)){
|
||||
Ok(d) => Ok(d),
|
||||
Err(e) => Err(dbg!(e)),
|
||||
}.ok()),
|
||||
);
|
||||
Ok(())
|
||||
pub fn enumerate(handle: &mut WebviewMut) -> Result<Vec<AddDevice>, libnspire::Error> {
|
||||
let devices: Vec<_> = rusb::devices()?.iter().collect();
|
||||
let mut map = crate::DEVICES.write().unwrap();
|
||||
map
|
||||
.drain_filter(|k, _v| {
|
||||
devices
|
||||
.iter()
|
||||
.all(|d| d.bus_number() != k.0 || d.address() != k.1)
|
||||
})
|
||||
.for_each(|d| {
|
||||
if let Err(msg) = tauri::event::emit(
|
||||
handle,
|
||||
"removeDevice",
|
||||
Some(DevId {
|
||||
bus_number: (d.0).0,
|
||||
address: (d.0).1,
|
||||
}),
|
||||
) {
|
||||
eprintln!("{}", msg);
|
||||
}
|
||||
});
|
||||
let filtered: Vec<_> = devices
|
||||
.into_iter()
|
||||
.filter(|d| !map.contains_key(&(d.bus_number(), d.address())))
|
||||
.collect();
|
||||
Ok(
|
||||
filtered
|
||||
.into_iter()
|
||||
.filter_map(|dev| {
|
||||
match add_device(Arc::new(dev)) {
|
||||
Ok(d) => Ok(d),
|
||||
Err(e) => Err(dbg!(e)),
|
||||
}
|
||||
.ok()
|
||||
})
|
||||
.map(|dev| {
|
||||
let msg = AddDevice {
|
||||
dev: dbg!(DevId {
|
||||
bus_number: (dev.0).0,
|
||||
address: (dev.0).1,
|
||||
}),
|
||||
name: (dev.1).name.clone(),
|
||||
is_cx_ii: (dev.1)
|
||||
.device
|
||||
.device_descriptor()
|
||||
.map(|d| d.product_id() == PID_CX2)
|
||||
.unwrap_or(false),
|
||||
needs_drivers: (dev.1).needs_drivers,
|
||||
};
|
||||
map.insert(dev.0, dev.1);
|
||||
msg
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
@ -185,6 +248,7 @@ pub struct AddDevice {
|
|||
pub dev: DevId,
|
||||
pub name: String,
|
||||
pub is_cx_ii: bool,
|
||||
pub needs_drivers: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// windows_subsystem = "windows"
|
||||
// )]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use hashbrown::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
@ -35,6 +35,7 @@ pub struct Device {
|
|||
name: String,
|
||||
device: Arc<rusb::Device<GlobalContext>>,
|
||||
state: DeviceState,
|
||||
needs_drivers: bool,
|
||||
}
|
||||
lazy_static::lazy_static! {
|
||||
static ref DEVICES: RwLock<HashMap<(u8, u8), Device>> = RwLock::new(HashMap::new());
|
||||
|
@ -55,6 +56,7 @@ impl Hotplug<GlobalContext> for DeviceMon {
|
|||
match add_device(device.clone()) {
|
||||
Ok(dev) => {
|
||||
let name = (dev.1).name.clone();
|
||||
let needs_drivers = (dev.1).needs_drivers;
|
||||
DEVICES.write().unwrap().insert(dev.0, dev.1);
|
||||
if let Err(msg) = tauri::event::emit(
|
||||
&mut handle,
|
||||
|
@ -66,6 +68,7 @@ impl Hotplug<GlobalContext> for DeviceMon {
|
|||
},
|
||||
name,
|
||||
is_cx_ii,
|
||||
needs_drivers,
|
||||
}),
|
||||
) {
|
||||
eprintln!("{}", msg);
|
||||
|
@ -85,23 +88,43 @@ impl Hotplug<GlobalContext> for DeviceMon {
|
|||
}
|
||||
|
||||
fn device_left(&mut self, device: rusb::Device<GlobalContext>) {
|
||||
if let Some((dev, _)) = DEVICES
|
||||
// if let Some((dev, _)) = DEVICES
|
||||
// .write()
|
||||
// .unwrap()
|
||||
// .remove_entry(&(device.bus_number(), device.address()))
|
||||
// {
|
||||
// if let Err(msg) = tauri::event::emit(
|
||||
// &mut self.handle,
|
||||
// "removeDevice",
|
||||
// Some(DevId {
|
||||
// bus_number: dev.0,
|
||||
// address: dev.1,
|
||||
// }),
|
||||
// ) {
|
||||
// eprintln!("{}", msg);
|
||||
// };
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
fn err_wrap<T>(
|
||||
res: Result<T, libnspire::Error>,
|
||||
dev: DevId,
|
||||
handle: &mut WebviewMut,
|
||||
) -> Result<T, libnspire::Error> {
|
||||
if let Err(ref e) = res {
|
||||
dbg!(e);
|
||||
}
|
||||
if let Err(libnspire::Error::NoDevice) = res {
|
||||
DEVICES
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove_entry(&(device.bus_number(), device.address()))
|
||||
{
|
||||
if let Err(msg) = tauri::event::emit(
|
||||
&mut self.handle,
|
||||
"removeDevice",
|
||||
Some(DevId {
|
||||
bus_number: dev.0,
|
||||
address: dev.1,
|
||||
}),
|
||||
) {
|
||||
eprintln!("{}", msg);
|
||||
};
|
||||
}
|
||||
.remove(&(dev.bus_number, dev.address));
|
||||
if let Err(msg) = tauri::event::emit(handle, "removeDevice", Some(dev)) {
|
||||
eprintln!("{}", msg);
|
||||
};
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn progress_sender<'a>(
|
||||
|
@ -180,28 +203,7 @@ fn main() {
|
|||
}
|
||||
promise_fn(
|
||||
webview,
|
||||
move || {
|
||||
let _ = cmd::enumerate();
|
||||
Ok(
|
||||
DEVICES
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|dev| AddDevice {
|
||||
dev: dbg!(DevId {
|
||||
bus_number: (dev.0).0,
|
||||
address: (dev.0).1,
|
||||
}),
|
||||
name: (dev.1).name.clone(),
|
||||
is_cx_ii: (dev.1)
|
||||
.device
|
||||
.device_descriptor()
|
||||
.map(|d| d.product_id() == PID_CX2)
|
||||
.unwrap_or(false),
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
},
|
||||
move || Ok(cmd::enumerate(&mut wv_handle)?),
|
||||
promise,
|
||||
);
|
||||
}
|
||||
|
@ -253,8 +255,7 @@ fn main() {
|
|||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
|
||||
let info = handle.info()?;
|
||||
let info = err_wrap(handle.info(), dev, &mut wv_handle)?;
|
||||
Ok(info)
|
||||
},
|
||||
promise,
|
||||
|
@ -266,7 +267,7 @@ fn main() {
|
|||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
let dir = handle.list_dir(&path)?;
|
||||
let dir = err_wrap(handle.list_dir(&path), dev, &mut wv_handle)?;
|
||||
|
||||
Ok(
|
||||
dir
|
||||
|
@ -296,10 +297,14 @@ fn main() {
|
|||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
let mut buf = vec![0; size as usize];
|
||||
handle.read_file(
|
||||
&file,
|
||||
&mut buf,
|
||||
&mut progress_sender(&mut wv_handle, dev, size as usize),
|
||||
err_wrap(
|
||||
handle.read_file(
|
||||
&file,
|
||||
&mut buf,
|
||||
&mut progress_sender(&mut wv_handle.clone(), dev, size as usize),
|
||||
),
|
||||
dev,
|
||||
&mut wv_handle,
|
||||
)?;
|
||||
if let Some(name) = file.split('/').last() {
|
||||
File::create(dest.join(name))?.write_all(&buf)?;
|
||||
|
@ -328,10 +333,14 @@ fn main() {
|
|||
.ok_or_else(|| anyhow::anyhow!("Failed to get file name"))?
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
handle.write_file(
|
||||
&format!("{}/{}", path, name),
|
||||
&buf,
|
||||
&mut progress_sender(&mut wv_handle, dev, buf.len()),
|
||||
err_wrap(
|
||||
handle.write_file(
|
||||
&format!("{}/{}", path, name),
|
||||
&buf,
|
||||
&mut progress_sender(&mut wv_handle.clone(), dev, buf.len()),
|
||||
),
|
||||
dev,
|
||||
&mut wv_handle,
|
||||
)?;
|
||||
Ok(())
|
||||
},
|
||||
|
@ -346,7 +355,14 @@ fn main() {
|
|||
let handle = handle.lock().unwrap();
|
||||
let mut buf = vec![];
|
||||
File::open(&src)?.read_to_end(&mut buf)?;
|
||||
handle.send_os(&buf, &mut progress_sender(&mut wv_handle, dev, buf.len()))?;
|
||||
err_wrap(
|
||||
handle.send_os(
|
||||
&buf,
|
||||
&mut progress_sender(&mut wv_handle.clone(), dev, buf.len()),
|
||||
),
|
||||
dev,
|
||||
&mut wv_handle,
|
||||
)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
|
@ -358,7 +374,7 @@ fn main() {
|
|||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
handle.delete_file(&path)?;
|
||||
err_wrap(handle.delete_file(&path), dev, &mut wv_handle)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
|
@ -370,7 +386,7 @@ fn main() {
|
|||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
handle.delete_dir(&path)?;
|
||||
err_wrap(handle.delete_dir(&path), dev, &mut wv_handle)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
|
@ -382,7 +398,7 @@ fn main() {
|
|||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
handle.create_dir(&path)?;
|
||||
err_wrap(handle.create_dir(&path), dev, &mut wv_handle)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
|
@ -399,7 +415,7 @@ fn main() {
|
|||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
handle.move_file(&src, &dest)?;
|
||||
err_wrap(handle.move_file(&src, &dest), dev, &mut wv_handle)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
|
@ -416,7 +432,7 @@ fn main() {
|
|||
move || {
|
||||
let handle = get_open_dev(&dev)?;
|
||||
let handle = handle.lock().unwrap();
|
||||
handle.copy_file(&src, &dest)?;
|
||||
err_wrap(handle.copy_file(&src, &dest), dev, &mut wv_handle)?;
|
||||
Ok(())
|
||||
},
|
||||
promise,
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
},
|
||||
"allowlist": {
|
||||
"event": true,
|
||||
"notification": true
|
||||
"notification": true,
|
||||
"open": true
|
||||
},
|
||||
"window": {
|
||||
"title": "N-Link",
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<div class="header border-b px-2 py-2">
|
||||
<el-popover width="239" :visible-arrow="false" popper-class="focus:outline-none dev-select-pop" v-model="active">
|
||||
<div slot="reference" class="inline-block relative w-full focus:outline-none">
|
||||
<div class="header border-b px-2 py-2 flex w-full">
|
||||
<button @click="$devices.enumerate()" class="flex-shrink-0 mr-2 focus:outline-none" :class="$devices.enumerating && 'cursor-not-allowed opacity-25'" :disabled="$devices.enumerating">
|
||||
<img src="~feather-icons/dist/icons/refresh-cw.svg" class="w-5"/>
|
||||
</button>
|
||||
<el-popover width="239" :visible-arrow="false" popper-class="focus:outline-none dev-select-pop" v-model="active" class="w-full overflow-hidden">
|
||||
<div slot="reference" class="relative w-full focus:outline-none">
|
||||
<div
|
||||
class="block w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-3/2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline h-8 truncate">
|
||||
<span v-if="selectedCalculator">
|
||||
|
@ -10,7 +13,7 @@
|
|||
<span v-else> {{ calc.name }}</span>
|
||||
</span>
|
||||
<span v-else class="text-gray-700 text-sm">
|
||||
Select...
|
||||
Select a device...
|
||||
</span>
|
||||
</div>
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<file-view v-if="!loading && files" :files="files" :show-hidden="showHidden" @nav="path = $event"
|
||||
@select="selected = $event"/>
|
||||
<div v-else-if="loading" class="flex items-center justify-center h-full">
|
||||
<div class="lds-dual-ring" />
|
||||
<div class="lds-dual-ring"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-auto h-full px-4 pt-4 w-48 flex-shrink-0 border-l">
|
||||
|
@ -98,6 +98,7 @@ export default class FileBrowser extends Vue {
|
|||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.lds-dual-ring:after {
|
||||
$color: theme('colors.gray.400');
|
||||
content: " ";
|
||||
|
|
|
@ -50,7 +50,7 @@ export type PartialCmd = { action: 'download'; path: [string, number]; dest: str
|
|||
|
||||
export type Cmd = { id: number } & PartialCmd;
|
||||
|
||||
export type Device = { name: string; info?: Info; progress?: Progress; queue?: Cmd[]; running?: boolean };
|
||||
export type Device = { name: string; isCxIi: boolean; needsDrivers: boolean; info?: Info; progress?: Progress; queue?: Cmd[]; running?: boolean };
|
||||
|
||||
async function downloadFile(dev: DevId | string, path: [string, number], dest: string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
|
@ -118,18 +118,16 @@ let queueId = 0;
|
|||
@Component
|
||||
class Devices extends Vue {
|
||||
devices: Record<string, Device> = {};
|
||||
enumerating = false;
|
||||
|
||||
created() {
|
||||
promisified({cmd: 'enumerate'}).then(devs => {
|
||||
for (const dev of devs as (Device & DevId)[]) {
|
||||
this.$set(this.devices, devToString(dev as DevId), dev);
|
||||
}
|
||||
}, console.error);
|
||||
this.enumerate().catch(console.error);
|
||||
listen('addDevice', dev => {
|
||||
const payload = dev.payload as Device & DevId;
|
||||
const str = devToString(payload);
|
||||
const existing = this.devices[str] || {};
|
||||
this.$set(this.devices, str, {...existing, ...payload});
|
||||
console.log(dev);
|
||||
});
|
||||
listen('removeDevice', dev => {
|
||||
this.$delete(this.devices, devToString(dev.payload as DevId));
|
||||
|
@ -193,6 +191,18 @@ class Devices extends Vue {
|
|||
this.runQueue(dev);
|
||||
}
|
||||
|
||||
async enumerate() {
|
||||
this.enumerating = true;
|
||||
try {
|
||||
for (const dev of await promisified({cmd: 'enumerate'}) as (Device & DevId)[]) {
|
||||
this.$set(this.devices, devToString(dev as DevId), dev);
|
||||
console.log(dev);
|
||||
}
|
||||
}finally {
|
||||
this.enumerating = false;
|
||||
}
|
||||
}
|
||||
|
||||
async open(dev: DevId | string) {
|
||||
if (typeof dev === 'string') dev = stringToDev(dev);
|
||||
const info = await promisified({...dev, cmd: 'openDevice'});
|
||||
|
|
|
@ -4,7 +4,17 @@
|
|||
<div class="flex-shrink-0 border-r w-64">
|
||||
<device-select :selected.sync="selectedCalculator"/>
|
||||
<div class="overflow-auto h-full px-4 py-4">
|
||||
<div v-if="calculator && calculator.info">
|
||||
<div v-if="needsDrivers">
|
||||
<h1 class="text-3xl">Drivers required</h1>
|
||||
<p>The WinUSB driver is required to use this device.</p>
|
||||
<p class="text-center mt-2">
|
||||
<a href="#" @click.prevent="installDrivers" class="text-blue-600">See installation instructions</a>
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="calculator && !calculator.info" class="flex items-center justify-center h-full">
|
||||
<div class="lds-dual-ring" />
|
||||
</div>
|
||||
<div v-else-if="calculator && calculator.info">
|
||||
<calc-info :info="calculator.info" :dev="selectedCalculator"/>
|
||||
<label class="inline-flex items-center cursor-pointer mr-2 mt-4">
|
||||
<input type="checkbox" class="form-checkbox h-5 w-5 text-blue-600 cursor-pointer" v-model="showHidden">
|
||||
|
@ -24,6 +34,7 @@
|
|||
|
||||
<script lang="ts">
|
||||
import {Component, Vue, Watch} from 'vue-property-decorator';
|
||||
import {open} from 'tauri/api/window';
|
||||
import CalcInfo from '@/components/CalcInfo.vue';
|
||||
import FileBrowser from '@/components/FileBrowser.vue';
|
||||
import DeviceSelect from "@/components/DeviceSelect.vue";
|
||||
|
@ -52,15 +63,49 @@ export default class Home extends Vue {
|
|||
}
|
||||
|
||||
@Watch('selectedCalculator')
|
||||
onSelectCalculator(dev: string | null) {
|
||||
if (dev && !this.$devices.devices[dev].info) {
|
||||
async onSelectCalculator(dev: string | null) {
|
||||
if (dev && !this.$devices.devices[dev].info && !this.$devices.devices[dev].needsDrivers) {
|
||||
console.log('open', dev);
|
||||
this.$devices.open(dev);
|
||||
try {
|
||||
await this.$devices.open(dev);
|
||||
}catch(e) {
|
||||
console.error(e);
|
||||
this.selectedCalculator = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get calculator() {
|
||||
return this.selectedCalculator && this.$devices.devices[this.selectedCalculator];
|
||||
}
|
||||
|
||||
get needsDrivers() {
|
||||
return this.selectedCalculator && this.$devices.devices[this.selectedCalculator]?.needsDrivers;
|
||||
}
|
||||
|
||||
installDrivers() {
|
||||
open('https://github.com');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.lds-dual-ring {
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.lds-dual-ring:after {
|
||||
$color: theme('colors.gray.400');
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 8px;
|
||||
border-radius: 50%;
|
||||
border: 6px solid $color;
|
||||
border-color: $color transparent $color transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue