|
|
|
@@ -1,6 +1,6 @@
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, ref } from "vue";
|
|
|
|
|
import type { AppDiscoveredCandidate, NewPrinter } from "../types";
|
|
|
|
|
import type { AppDiscoveredCandidate, NewPrinter, PrinterKind } from "../types";
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
|
gatewayOnline: boolean | null;
|
|
|
|
@@ -10,6 +10,15 @@ const props = defineProps<{
|
|
|
|
|
add: (printer: NewPrinter) => Promise<void>;
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
const KINDS: { value: PrinterKind; label: string }[] = [
|
|
|
|
|
{ value: "bambu", label: "Bambu Lab" },
|
|
|
|
|
{ value: "klipper", label: "Klipper (Moonraker)" },
|
|
|
|
|
{ value: "prusa_link", label: "Prusa (PrusaLink)" },
|
|
|
|
|
{ value: "octoprint", label: "OctoPrint" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const vendor = ref<PrinterKind>("bambu");
|
|
|
|
|
const apiKey = ref("");
|
|
|
|
|
const name = ref("");
|
|
|
|
|
const host = ref("");
|
|
|
|
|
const serialNumber = ref("");
|
|
|
|
@@ -18,11 +27,14 @@ const busy = ref(false);
|
|
|
|
|
const failure = ref("");
|
|
|
|
|
const added = ref("");
|
|
|
|
|
|
|
|
|
|
// Only Bambu has a working driver; the others are listed by discovery but
|
|
|
|
|
// cannot be driven yet, so offering them would add a printer that does nothing.
|
|
|
|
|
const usable = computed(() => props.candidates.filter((c) => c.vendor.startsWith("bambu") && !c.matchedOn));
|
|
|
|
|
// Discovery names the vendors the same way the form does, except that a
|
|
|
|
|
// Bambu may be reported as `bambu_v1`/`bambu_v2`.
|
|
|
|
|
const kindOf = (vendorName: string): PrinterKind | undefined =>
|
|
|
|
|
vendorName.startsWith("bambu") ? "bambu" : KINDS.find((k) => k.value === vendorName)?.value;
|
|
|
|
|
const usable = computed(() => props.candidates.filter((c) => kindOf(c.vendor) && !c.matchedOn));
|
|
|
|
|
|
|
|
|
|
function fill(candidate: AppDiscoveredCandidate) {
|
|
|
|
|
vendor.value = kindOf(candidate.vendor) ?? vendor.value;
|
|
|
|
|
host.value = candidate.address;
|
|
|
|
|
serialNumber.value = candidate.serial ?? "";
|
|
|
|
|
name.value = candidate.name || candidate.model || name.value;
|
|
|
|
@@ -33,12 +45,14 @@ async function submit() {
|
|
|
|
|
failure.value = "";
|
|
|
|
|
added.value = "";
|
|
|
|
|
try {
|
|
|
|
|
await props.add({
|
|
|
|
|
name: name.value.trim(),
|
|
|
|
|
host: host.value.trim(),
|
|
|
|
|
serialNumber: serialNumber.value.trim(),
|
|
|
|
|
accessCode: accessCode.value.trim(),
|
|
|
|
|
});
|
|
|
|
|
const printer: NewPrinter = { vendor: vendor.value, name: name.value.trim(), host: host.value.trim() };
|
|
|
|
|
if (vendor.value === "bambu") {
|
|
|
|
|
printer.serialNumber = serialNumber.value.trim();
|
|
|
|
|
printer.accessCode = accessCode.value.trim();
|
|
|
|
|
} else if (vendor.value !== "klipper") {
|
|
|
|
|
printer.apiKey = apiKey.value.trim();
|
|
|
|
|
}
|
|
|
|
|
await props.add(printer);
|
|
|
|
|
added.value = name.value;
|
|
|
|
|
name.value = host.value = serialNumber.value = "";
|
|
|
|
|
} catch (err) {
|
|
|
|
@@ -47,6 +61,7 @@ async function submit() {
|
|
|
|
|
// Cleared on success AND failure: the code should not sit in a form
|
|
|
|
|
// after it has been sent, and retyping eight digits is cheap.
|
|
|
|
|
accessCode.value = "";
|
|
|
|
|
apiKey.value = "";
|
|
|
|
|
busy.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -54,9 +69,9 @@ async function submit() {
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<section class="add-printer" aria-label="Add a printer">
|
|
|
|
|
<h3>Add a Bambu printer</h3>
|
|
|
|
|
<h3>Add a printer</h3>
|
|
|
|
|
<p v-if="gatewayOnline === false">
|
|
|
|
|
The gateway is not connected. The printer's access code is handed to the gateway and kept only there, so it
|
|
|
|
|
The gateway is not connected. The printer's credentials are handed to the gateway and kept only there, so it
|
|
|
|
|
has to be online to add a printer.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
@@ -68,16 +83,33 @@ async function submit() {
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<form @submit.prevent="submit">
|
|
|
|
|
<label>Name <input v-model="name" required maxlength="200" placeholder="Bench P1S" /></label>
|
|
|
|
|
<label>
|
|
|
|
|
Kind
|
|
|
|
|
<select v-model="vendor">
|
|
|
|
|
<option v-for="k in KINDS" :key="k.value" :value="k.value">{{ k.label }}</option>
|
|
|
|
|
</select>
|
|
|
|
|
</label>
|
|
|
|
|
<label>Name <input v-model="name" required maxlength="200" placeholder="Bench printer" /></label>
|
|
|
|
|
<label>
|
|
|
|
|
Address
|
|
|
|
|
<input v-model="host" required maxlength="255" pattern="[A-Za-z0-9.:_\-]+" placeholder="192.168.0.20" />
|
|
|
|
|
</label>
|
|
|
|
|
<label>
|
|
|
|
|
<label v-if="vendor === 'bambu'">
|
|
|
|
|
Serial number
|
|
|
|
|
<input v-model="serialNumber" required minlength="5" maxlength="64" pattern="[A-Za-z0-9]+" />
|
|
|
|
|
</label>
|
|
|
|
|
<label>
|
|
|
|
|
<label v-if="vendor === 'octoprint' || vendor === 'prusa_link'">
|
|
|
|
|
API key
|
|
|
|
|
<input
|
|
|
|
|
v-model="apiKey"
|
|
|
|
|
required
|
|
|
|
|
type="password"
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
maxlength="256"
|
|
|
|
|
:placeholder="vendor === 'octoprint' ? 'from OctoPrint settings' : 'from PrusaLink settings'"
|
|
|
|
|
/>
|
|
|
|
|
</label>
|
|
|
|
|
<label v-if="vendor === 'bambu'">
|
|
|
|
|
Access code
|
|
|
|
|
<input
|
|
|
|
|
v-model="accessCode"
|
|
|
|
@@ -93,7 +125,7 @@ async function submit() {
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
<p class="add-printer__note">
|
|
|
|
|
The access code goes to your gateway and nowhere else; Continuum's servers do not keep it.
|
|
|
|
|
The access code or API key goes to your gateway and nowhere else; Continuum's servers do not keep it.
|
|
|
|
|
</p>
|
|
|
|
|
<p v-if="added" role="status" class="add-printer__ok">Added {{ added }}.</p>
|
|
|
|
|
<p v-if="failure" role="alert" class="add-printer__error">{{ failure }}</p>
|
|
|
|
|