Merge pull request #70 from dacarley/4

5 - Screen rotation support
This commit is contained in:
David A. Carley
2022-08-23 15:33:15 -07:00
committed by GitHub
26 changed files with 255 additions and 189 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "bbctrl", "name": "bbctrl",
"version": "1.0.10b2", "version": "1.0.10b4",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bbctrl", "name": "bbctrl",
"version": "1.0.10b2", "version": "1.0.10b4",
"license": "GPL-3.0+", "license": "GPL-3.0+",
"dependencies": { "dependencies": {
"browserify": "^17.0.0", "browserify": "^17.0.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "bbctrl", "name": "bbctrl",
"version": "1.0.10b2", "version": "1.0.10b4",
"homepage": "https://onefinitycnc.com/", "homepage": "https://onefinitycnc.com/",
"repository": "https://github.com/OneFinityCNC/onefinity", "repository": "https://github.com/OneFinityCNC/onefinity",
"license": "GPL-3.0+", "license": "GPL-3.0+",

View File

@@ -1,32 +0,0 @@
#!/bin/bash
if [ $# != 3 ]; then
echo "Usage: $0 <width> <height> <rotation>"
exit 1
fi
WIDTH="$1"
HEIGHT="$2"
ROTATION="$3"
if [[ ! "$WIDTH" =~ ^[0-9]+$ ]]; then
echo "Invalid width '$WIDTH'."
exit 1
fi
if [[ ! "$HEIGHT" =~ ^[0-9]+$ ]]; then
echo "Invalid height '$HEIGHT'."
exit 1
fi
if [[ ! "$ROTATION" =~ ^[0-3]$ ]]; then
echo "Invalid rotation '$ROTATION'."
exit 1
fi
OPTIONS="framebuffer_width=$WIDTH "
OPTIONS+="framebuffer_height=$HEIGHT "
OPTIONS+="display_rotate=$ROTATION"
edit-boot-config $OPTIONS

View File

@@ -29,8 +29,7 @@ if $UPDATE_AVR; then
fi fi
# Update config.txt # Update config.txt
./scripts/edit-boot-config max_usb_current=1 ./scripts/edit-boot-config max_usb_current=1 config_hdmi_boost=8 hdmi_force_hotplug=1 hdmi_group=2 hdmi_mode=82
./scripts/edit-boot-config config_hdmi_boost=8
# TODO Enable GPU # TODO Enable GPU
#./scripts/edit-boot-config dtoverlay=vc4-kms-v3d #./scripts/edit-boot-config dtoverlay=vc4-kms-v3d

View File

@@ -34,7 +34,6 @@ setup(
'scripts/sethostname', 'scripts/sethostname',
'scripts/reset-video', 'scripts/reset-video',
'scripts/config-wifi', 'scripts/config-wifi',
'scripts/config-screen',
'scripts/edit-config', 'scripts/edit-config',
'scripts/edit-boot-config', 'scripts/edit-boot-config',
'scripts/browser', 'scripts/browser',

View File

@@ -21,5 +21,11 @@ module.exports = {
this.$dispatch('config-changed'); this.$dispatch('config-changed');
return false; return false;
} }
},
methods: {
showScreenRotationDialog: function () {
SvelteComponents.showDialog("ScreenRotation");
}
} }
} }

View File

@@ -3,7 +3,6 @@ html(lang="en")
head head
meta(charset="utf-8") meta(charset="utf-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0") meta(name="viewport", content="width=device-width, initial-scale=1.0")
link(rel="preload" href="/fonts/material-symbols-outlined.woff2" as="font" type="font/woff2" crossorigin)
title Onefinity CNC - Web interface title Onefinity CNC - Web interface
@@ -16,7 +15,6 @@ html(lang="en")
style: include ../svelte-components/node_modules/svelte-material-ui/bare.css style: include ../svelte-components/node_modules/svelte-material-ui/bare.css
style: include ../../build/http/svelte-components/smui.css style: include ../../build/http/svelte-components/smui.css
style: include ../../build/http/svelte-components/style.css style: include ../../build/http/svelte-components/style.css
style: include ../../build/http/svelte-components/material-symbols-outlined.css
style: include:stylus ../stylus/style.styl style: include:stylus ../stylus/style.styl
body(v-cloak) body(v-cloak)

View File

@@ -61,7 +61,7 @@ script#control-view-template(type="text/x-template")
button(@click="jog_fn(-1,0,0,0)") X- button(@click="jog_fn(-1,0,0,0)") X-
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(@click="ask_zero_xy_msg = true") button(@click="ask_zero_xy_msg = true")
.fa.fa-bullseye(style="font-size: 172%") .fa.fa-bullseye(style="font-size: 173%")
td(style="height:100px",align="center") td(style="height:100px",align="center")
button(@click="jog_fn(1,0,0,0)") X+ button(@click="jog_fn(1,0,0,0)") X+
td(style="height:100px",align="center") td(style="height:100px",align="center")
@@ -221,7 +221,7 @@ script#control-view-template(type="text/x-template")
| {{message.replace(/^#/, '')}} | {{message.replace(/^#/, '')}}
tr tr
th Units th Display Units
td.units td.units
select(v-model="display_units") select(v-model="display_units")
option(value="METRIC") METRIC option(value="METRIC") METRIC

View File

@@ -4,16 +4,14 @@ script#settings-view-template(type="text/x-template")
.pure-form.pure-form-aligned .pure-form.pure-form-aligned
fieldset fieldset
h2 Units h2 Screen
.pure-control-group .pure-control-group
label(for="units") units label(for="screen-rotation")
select(name="units", v-model="display_units") button.pure-button(name="screen-rotation", @click="showScreenRotationDialog") Change Screen Rotation
option(value="METRIC") METRIC
option(value="IMPERIAL") IMPERIAL
fieldset fieldset
h2 Probe Dimensions h2 Probe Dimensions
templated-input(v-for="templ in template.probe", :name="$key", templated-input(v-for="templ in template.probe", v-if="$key !== 'probe-diameter'", :name="$key"
:model.sync="config.probe[$key]", :template="templ") :model.sync="config.probe[$key]", :template="templ")
fieldset fieldset

View File

@@ -71,6 +71,7 @@ class Ctrl(object):
def configure(self): def configure(self):
# Indirectly configures state via calls to config() and the AVR # Indirectly configures state via calls to config() and the AVR
self.config.reload() self.config.reload()
self.state.init()
def ready(self): def ready(self):
# This is used to synchronize the start of the preplanner # This is used to synchronize the start of the preplanner

View File

@@ -419,3 +419,12 @@ class State(object):
if switch[1:] == '-max': if switch[1:] == '-max':
return self.get_axis_switch(switch[0], 'max') return self.get_axis_switch(switch[0], 'max')
raise Exception('Unsupported switch "%s"' % switch) raise Exception('Unsupported switch "%s"' % switch)
def init(self):
# Init machine units
metric = self.ctrl.config.get('units', 'METRIC').upper() == 'METRIC'
self.log.info('INIT Metric %d' % metric)
if not 'metric' in self.vars:
self.set('metric', metric)
if not 'imperial' in self.vars:
self.set('imperial', not metric)

View File

@@ -1,4 +1,5 @@
import os import os
import re
import json import json
import tornado import tornado
import sockjs.tornado import sockjs.tornado
@@ -349,6 +350,46 @@ class JogHandler(bbctrl.APIHandler):
self.get_ctrl().mach.jog(self.json) self.get_ctrl().mach.jog(self.json)
displayRotatePattern = re.compile(r'display_rotate\s*=\s*(\d)')
transformationMatrixPattern = re.compile(
r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(\s+Option\s+\"TransformationMatrix\".*?\n)(.*?EndSection)', re.DOTALL)
matchIsTouchscreenPattern = re.compile(
r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(.*?EndSection)', re.DOTALL)
class ScreenRotationHandler(bbctrl.APIHandler):
@gen.coroutine
def get(self):
with open("/boot/config.txt", 'rt') as config:
lines = config.readlines()
for line in lines:
if line.startswith('display_rotate'):
self.write_json({
'rotated': int(displayRotatePattern.search(line).group(1)) != 0
})
return
self.write_json({'rotated': False})
return
@gen.coroutine
def put_ok(self):
rotated = self.json['rotated']
subprocess.Popen(
['/usr/local/bin/edit-boot-config', 'display_rotate={}'.format(2 if rotated else 0)])
with open("/usr/share/X11/xorg.conf.d/40-libinput.conf", 'rt') as config:
text = config.read()
text = transformationMatrixPattern.sub(r'\1\2\3\5', text)
if rotated:
text = matchIsTouchscreenPattern.sub(r'\1\2\3\2Option "TransformationMatrix" "-1 0 1 0 -1 1 0 0 1"\1\4', text)
with open("/usr/share/X11/xorg.conf.d/40-libinput.conf", 'wt') as config:
config.write(text)
subprocess.run('reboot')
# Base class for Web Socket connections # Base class for Web Socket connections
class ClientConnection(object): class ClientConnection(object):
def __init__(self, app): def __init__(self, app):
@@ -484,6 +525,7 @@ class Web(tornado.web.Application):
(r'/api/modbus/write', ModbusWriteHandler), (r'/api/modbus/write', ModbusWriteHandler),
(r'/api/jog', JogHandler), (r'/api/jog', JogHandler),
(r'/api/video', bbctrl.VideoHandler), (r'/api/video', bbctrl.VideoHandler),
(r'/api/screen-rotation', ScreenRotationHandler),
(r'/(.*)', StaticFileHandler, (r'/(.*)', StaticFileHandler,
{'path': bbctrl.get_resource('http/'), {'path': bbctrl.get_resource('http/'),
'default_filename': 'index.html'}), 'default_filename': 'index.html'}),

View File

@@ -4,6 +4,9 @@ body
[v-cloak] [v-cloak]
display none display none
.menu-link
z-index unset
tt tt
color #000 color #000
background #eee background #eee
@@ -356,6 +359,7 @@ span.unit
.axis .axis
.name .name
text-transform capitalize text-transform capitalize
vertical-align middle
.name, .position .name, .position
font-size 24pt font-size 24pt

View File

@@ -1,28 +0,0 @@
/* To browse the icons in material-symbols, see https://marella.me/material-symbols/demo/ */
@font-face {
font-family: "Material Symbols Outlined";
font-style: normal;
font-weight: 100 700;
font-display: block;
src: url("./fonts/material-symbols-outlined.woff2") format("woff2");
}
.material-symbols-outlined {
font-family: "Material Symbols Outlined";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
font-feature-settings: "liga";
font-variation-settings: "FILL" 1, "wght" 400, "GRAD" 0, "opsz" 48;
}

View File

@@ -15,21 +15,24 @@
network: {} as WifiNetwork, network: {} as WifiNetwork,
}; };
function getWifiStrengthIcon(network: WifiNetwork) { function getWifiStrengthStyle(network: WifiNetwork) {
const strength = Math.ceil((Number(network.Quality) / 100) * 4); const strength = Math.ceil((Number(network.Quality) / 100) * 4);
switch (strength) { switch (strength) {
case 0:
return "clip-path: circle(0px at 12.5px 19px);";
case 1: case 1:
return ""; return "clip-path: circle(4px at 12.5px 19px);";
case 2: case 2:
return "wifi_1_bar"; return "clip-path: circle(8px at 12.5px 19px);";
case 3: case 3:
return "wifi_2_bar"; return "clip-path: circle(14px at 12.5px 19px);";
case 4: case 4:
return "wifi"; return "";
} }
} }
@@ -103,17 +106,16 @@
? 'active' ? 'active'
: ''}" : ''}"
> >
<span class="material-symbols-outlined background" <span class="fa fa-wifi background" />
>wifi</span <span
> class="fa fa-wifi"
<span class="material-symbols-outlined"> style={getWifiStrengthStyle(network)}
{getWifiStrengthIcon(network)} />
</span>
</Graphic> </Graphic>
<Text style="margin-right: 20px;">{network.Name}</Text> <Text style="margin-right: 20px;">{network.Name}</Text>
{#if network.Encryption !== "Open"} {#if network.Encryption !== "Open"}
<Meta> <Meta>
<span class="material-symbols-outlined lock">lock</span> <span class="fa fa-lock" />
</Meta> </Meta>
{/if} {/if}
</Item> </Item>
@@ -188,8 +190,8 @@
span { span {
position: absolute; position: absolute;
width: 24px; top: 5px;
height: 24px; font-size: 22px;
&.background { &.background {
opacity: 0.25; opacity: 0.25;

View File

@@ -1,5 +1,15 @@
<script lang="ts"> <script lang="ts">
import * as api from "$lib/api"; import * as api from "$lib/api";
import { onMount } from "svelte";
onMount(() => {
document.body.style.backgroundColor = "black";
const layout = document.querySelector("#layout") as HTMLElement;
layout.style.backgroundColor = "white";
layout.style.width = "1280px";
layout.style.height = "720px";
layout.style.overflowY = "scroll";
});
</script> </script>
<div class="devmode"> <div class="devmode">

View File

@@ -11,6 +11,7 @@
type Option = { type Option = {
value: number; value: number;
label: string;
metric: boolean; metric: boolean;
}; };
@@ -65,10 +66,7 @@
<List> <List>
{#each options as option} {#each options as option}
<Item on:SMUI:action={() => onOptionSelected(option)}> <Item on:SMUI:action={() => onOptionSelected(option)}>
<Text> <Text>{option.label}</Text>
{option.value}
{option.metric ? "mm" : "in"}
</Text>
</Item> </Item>
{/each} {/each}
</List> </List>
@@ -76,18 +74,18 @@
</div> </div>
<style lang="scss"> <style lang="scss">
.value-and-unit {
display: flex;
column-gap: 10px;
}
:global { :global {
.mdc-select { .value-and-unit {
max-width: 60px; display: flex;
} column-gap: 10px;
.smui-select--standard .mdc-select__dropdown-icon { .mdc-select {
margin-left: 0; max-width: 60px;
}
.smui-select--standard .mdc-select__dropdown-icon {
margin-left: 0;
}
} }
} }
</style> </style>

View File

@@ -64,12 +64,12 @@
<Dialog <Dialog
bind:open bind:open
scrimClickAction="" scrimClickAction=""
aria-labelledby="simple-title" aria-labelledby="change-hostname-dialog-title"
aria-describedby="simple-content" aria-describedby="change-hostname-dialog-content"
> >
<Title id="simple-title">Change Hostname</Title> <Title id="change-hostname-dialog-title">Change Hostname</Title>
<Content id="simple-content"> <Content id="change-hostname-dialog-content">
<TextField <TextField
bind:value={hostname} bind:value={hostname}
label="New Hostname" label="New Hostname"
@@ -88,7 +88,7 @@
<Label>Cancel</Label> <Label>Cancel</Label>
</Button> </Button>
<Button defaultAction on:click={onConfirm} disabled={hostname.length === 0}> <Button defaultAction on:click={onConfirm} disabled={hostname.length === 0}>
<Label>Confirm</Label> <Label>Confirm & Reboot</Label>
</Button> </Button>
</Actions> </Actions>
</Dialog> </Dialog>

View File

@@ -1,31 +1,48 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
import { writable } from "svelte/store";
import HomeMachineDialog from "$dialogs/HomeMachineDialog.svelte"; import HomeMachineDialog from "$dialogs/HomeMachineDialog.svelte";
import ProbeDialog from "$dialogs/ProbeDialog.svelte"; import ProbeDialog from "$dialogs/ProbeDialog.svelte";
import { import ScreenRotationDialog from "$dialogs/ScreenRotationDialog.svelte";
HomeMachineProps,
ProbeProps, const HomeMachineDialogProps = writable<HomeMachineDialogPropsType>();
type HomeMachinePropsType, type HomeMachineDialogPropsType = {
type ProbePropsType, open: boolean;
} from "$dialogs/DialogProps"; home: () => void;
};
const ProbeDialogProps = writable<ProbeDialogPropsType>();
type ProbeDialogPropsType = {
open: boolean;
probeType: "xyz" | "z";
};
const ScreenRotationDialogProps = writable<ProbeDialogPropsType>();
type ScreenRotationDialogPropsType = {
open: boolean;
};
export function showDialog( export function showDialog(
dialog: "HomeMachine", dialog: "HomeMachine",
props: Omit<HomeMachinePropsType, "open"> props: Omit<HomeMachineDialogPropsType, "open">
); );
export function showDialog( export function showDialog(
dialog: "Probe", dialog: "Probe",
props: Omit<ProbePropsType, "open"> props: Omit<ProbeDialogPropsType, "open">
); );
export function showDialog(dialog: string, props: any) { export function showDialog(dialog: string, props: any) {
switch (dialog) { switch (dialog) {
case "HomeMachine": case "HomeMachine":
HomeMachineProps.set({ ...props, open: true }); HomeMachineDialogProps.set({ ...props, open: true });
break; break;
case "Probe": case "Probe":
ProbeProps.set({ ...props, open: true }); ProbeDialogProps.set({ ...props, open: true });
break;
case "ScreenRotation":
ScreenRotationDialogProps.set({ ...props, open: true });
break; break;
default: default:
@@ -34,5 +51,6 @@
} }
</script> </script>
<HomeMachineDialog {...$HomeMachineProps} /> <HomeMachineDialog {...$HomeMachineDialogProps} />
<ProbeDialog {...$ProbeProps} /> <ProbeDialog {...$ProbeDialogProps} />
<ScreenRotationDialog {...$ScreenRotationDialogProps} />

View File

@@ -1,13 +0,0 @@
import { writable } from "svelte/store";
export const HomeMachineProps = writable<HomeMachinePropsType>();
export type HomeMachinePropsType = {
open: boolean,
home: () => void
}
export const ProbeProps = writable<ProbePropsType>();
export type ProbePropsType = {
open: boolean,
probeType: "xyz" | "z"
};

View File

@@ -8,12 +8,12 @@
<Dialog <Dialog
bind:open bind:open
aria-labelledby="simple-title" aria-labelledby="home-machine-dialog-title"
aria-describedby="simple-content" aria-describedby="home-machine-dialog-content"
> >
<Title id="simple-title">Home Machine</Title> <Title id="home-machine-dialog-title">Home Machine</Title>
<Content id="simple-content">Home the machine?</Content> <Content id="home-machine-dialog-content">Home the machine?</Content>
<Actions> <Actions>
<Button> <Button>

View File

@@ -9,11 +9,11 @@
bind:open bind:open
scrimClickAction="" scrimClickAction=""
escapeKeyAction="" escapeKeyAction=""
aria-labelledby="simple-title" aria-labelledby="message-dialog-title"
aria-describedby="simple-content" aria-describedby="message-dialog-content"
> >
<Title id="simple-title">{title}</Title> <Title id="message-dialog-title">{title}</Title>
<Content id="simple-content"> <Content id="message-dialog-content">
<slot /> <slot />
</Content> </Content>
</Dialog> </Dialog>

View File

@@ -63,28 +63,18 @@
import { Config } from "$lib/ConfigStore"; import { Config } from "$lib/ConfigStore";
const cutterDiameterOptions = [ const cutterDiameterOptions = [
{ value: 0.5, metric: false }, { value: 0.5, label: '1/2 "', metric: false },
{ value: 10, metric: true }, { value: 0.25, label: '1/4 "', metric: false },
{ value: 0.25, metric: false }, { value: 0.125, label: '1/8 "', metric: false },
{ value: 6, metric: true }, { value: 10, label: "10 mm", metric: true },
{ value: 0.125, metric: false }, { value: 6, label: "6 mm", metric: true },
{ value: 3, metric: true }, { value: 3, label: "10 mm", metric: true },
];
const cutterLengthOptions = [
{ value: 1, metric: false },
{ value: 20, metric: true },
{ value: 0.5, metric: false },
{ value: 10, metric: true },
{ value: 0.25, metric: false },
{ value: 6, metric: true },
]; ];
export let open; export let open;
export let probeType: "xyz" | "z"; export let probeType: "xyz" | "z";
let currentStep: Step = "None"; let currentStep: Step = "None";
let cutterDiameter: number; let cutterDiameter: number;
let cutterLength: number;
let showCancelButton = true; let showCancelButton = true;
let steps: Array<Step> = []; let steps: Array<Step> = [];
let nextButton = { let nextButton = {
@@ -98,8 +88,6 @@
$: if (open) { $: if (open) {
cutterDiameter = cutterDiameter =
Number.parseFloat(localStorage.getItem("cutterDiameter")) || null; Number.parseFloat(localStorage.getItem("cutterDiameter")) || null;
cutterLength =
Number.parseFloat(localStorage.getItem("cutterLength")) || null;
// Svelte appears not to like it when you invoke // Svelte appears not to like it when you invoke
// an async function from a reactive statement, so we // an async function from a reactive statement, so we
@@ -107,7 +95,7 @@
requestAnimationFrame(begin); requestAnimationFrame(begin);
} }
$: if (cutterDiameter && cutterLength) { $: if (cutterDiameter) {
updateButtons(); updateButtons();
} }
@@ -132,8 +120,7 @@
if (probeType === "xyz") { if (probeType === "xyz") {
await stepCompleted("BitDimensions", userAcknowledged); await stepCompleted("BitDimensions", userAcknowledged);
localStorage.setItem("cutterDiameter", cutterDiameter); localStorage.setItem("cutterDiameter", cutterDiameter.toString());
localStorage.setItem("cutterLength", cutterLength);
} }
await stepCompleted("PlaceProbeBlock", userAcknowledged); await stepCompleted("PlaceProbeBlock", userAcknowledged);
@@ -221,10 +208,7 @@
nextButton.disabled = !( nextButton.disabled = !(
cutterDiameter !== null && cutterDiameter !== null &&
cutterDiameter !== 0 && cutterDiameter !== 0 &&
isFinite(cutterDiameter) && isFinite(cutterDiameter)
cutterLength !== null &&
cutterLength !== 0 &&
isFinite(cutterLength)
); );
break; break;
@@ -246,6 +230,7 @@
const slowSeek = $Config.probe["probe-slow-seek"]; const slowSeek = $Config.probe["probe-slow-seek"];
const fastSeek = $Config.probe["probe-fast-seek"]; const fastSeek = $Config.probe["probe-fast-seek"];
const cutterLength = 12.7;
const zLift = 1; const zLift = 1;
const xOffset = probeBlockWidth + cutterDiameter / 2.0; const xOffset = probeBlockWidth + cutterDiameter / 2.0;
const yOffset = probeBlockLength + cutterDiameter / 2.0; const yOffset = probeBlockLength + cutterDiameter / 2.0;
@@ -310,13 +295,13 @@
bind:open bind:open
class="probe-dialog" class="probe-dialog"
scrimClickAction="" scrimClickAction=""
aria-labelledby="simple-title" aria-labelledby="probe-dialog-title"
aria-describedby="simple-content" aria-describedby="probe-dialog-content"
surface$style="width: 700px; height: 400px; max-width: calc(100vw - 32px); overflow: visible;" surface$style="width: 700px; height: 400px; max-width: calc(100vw - 32px); overflow: visible;"
> >
<Title id="simple-title">Probing {probeType?.toUpperCase()}</Title> <Title id="probe-dialog-title">Probing {probeType?.toUpperCase()}</Title>
<Content id="simple-content" style="overflow: visible;"> <Content id="probe-dialog-content" style="overflow: visible;">
<div class="steps"> <div class="steps">
<p><b>Step {steps.indexOf(currentStep) + 1} of {steps.length}</b></p> <p><b>Step {steps.indexOf(currentStep) + 1} of {steps.length}</b></p>
<ul> <ul>
@@ -337,12 +322,6 @@
bind:value={cutterDiameter} bind:value={cutterDiameter}
{metric} {metric}
/> />
<DimensionInput
label="Cutter length"
options={cutterLengthOptions}
bind:value={cutterLength}
{metric}
/>
{:else if currentStep === "PlaceProbeBlock"} {:else if currentStep === "PlaceProbeBlock"}
{#if probeType === "xyz"} {#if probeType === "xyz"}
Place the probe block face up, on the lower-left corner of your Place the probe block face up, on the lower-left corner of your
@@ -402,7 +381,7 @@
$light: #ddd; $light: #ddd;
:global { :global {
.mdc-dialog__content { #probe-dialog-content {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }

View File

@@ -0,0 +1,76 @@
<script type="ts">
import Dialog, { Title, Content, Actions } from "@smui/dialog";
import Button, { Label } from "@smui/button";
import Radio from "@smui/radio";
import FormField from "@smui/form-field";
import MessageDialog from "$dialogs/MessageDialog.svelte";
import * as Api from "$lib/api";
import { onMount } from "svelte";
const options = [
{ value: 0, label: "Normal" },
{ value: 1, label: "Upside-down" },
];
export let open;
let currentValue;
let value;
let rebooting;
onMount(async () => {
const result = await Api.GET("screen-rotation");
currentValue = value = result.rotated ? 1 : 0;
});
async function onConfirm() {
rebooting = true;
await Api.PUT("screen-rotation", { rotated: value === 1 });
}
</script>
<MessageDialog open={rebooting} title="Rebooting">
Rebooting to apply the new screen rotation...
</MessageDialog>
<Dialog
bind:open
scrimClickAction=""
aria-labelledby="screen-rotation-dialog-title"
aria-describedby="screen-rotation-dialog-content"
>
<Title id="screen-rotation-dialog-title">Screen Rotation</Title>
<Content id="screen-rotation-dialog-content">
{#each options as option}
<FormField>
<Radio bind:group={value} value={option.value} />
<span slot="label">
{option.label}
</span>
</FormField>
{/each}
</Content>
<Actions>
<Button>
<Label>Cancel</Label>
</Button>
<Button
defaultAction
disabled={value === currentValue}
on:click={onConfirm}
>
<Label>Confirm & Reboot</Label>
</Button>
</Actions>
</Dialog>
<style lang="scss">
:global {
#screen-rotation-dialog-content {
display: flex;
flex-direction: column;
}
}
</style>

View File

@@ -18,8 +18,8 @@
$: needPassword = !network?.active && network?.Encryption !== "Open"; $: needPassword = !network?.active && network?.Encryption !== "Open";
$: connectOrDisconnect = network?.active ? "Disconnect" : "Connect"; $: connectOrDisconnect = network?.active ? "Disconnect" : "Connect";
$: connectToOrDisconnectFrom = network?.active $: connectToOrDisconnectFrom = network?.active
? "Disconnect from" ? "Disconnect from"
: "Connect to"; : "Connect to";
$: if (open) { $: if (open) {
password = ""; password = "";
@@ -45,12 +45,14 @@
<Dialog <Dialog
bind:open bind:open
scrimClickAction="" scrimClickAction=""
aria-labelledby="simple-title" aria-labelledby="wifi-connection-dialog-title"
aria-describedby="simple-content" aria-describedby="wifi-connection-dialog-content"
> >
<Title id="simple-title">{connectToOrDisconnectFrom} {network.Name}</Title> <Title id="wifi-connection-dialog-title"
>{connectToOrDisconnectFrom} {network.Name}</Title
>
<Content id="simple-content"> <Content id="wifi-connection-dialog-content">
{#if needPassword} {#if needPassword}
<TextField <TextField
bind:value={password} bind:value={password}
@@ -64,9 +66,7 @@
slot="trailingIcon" slot="trailingIcon"
on:click={() => (showPassword = !showPassword)} on:click={() => (showPassword = !showPassword)}
> >
<Icon class="material-symbols-outlined"> <Icon class={`fa ${showPassword ? "fa-eye-slash" : "fa-eye"}`} />
{showPassword ? "password" : "abc"}
</Icon>
</div> </div>
<HelperText persistent slot="helper"> <HelperText persistent slot="helper">
Wifi passwords must be 8 to 128 characters Wifi passwords must be 8 to 128 characters
@@ -92,7 +92,7 @@
on:click={onConfirm} on:click={onConfirm}
disabled={needPassword && (password.length < 8 || password.length > 128)} disabled={needPassword && (password.length < 8 || password.length > 128)}
> >
<Label>{connectOrDisconnect}</Label> <Label>{connectOrDisconnect} & Reboot</Label>
</Button> </Button>
</Actions> </Actions>
</Dialog> </Dialog>