n-link/n-link-core/components/CalcInfo.vue
Your Name b3d621cd6d feat(ui): restyle CalcInfo with AppButton and ProgressBar
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 20:31:19 -04:00

107 lines
3.4 KiB
Vue

<template>
<div>
<h2 class="text-center text-xl select-text">{{ info.name }}</h2>
<h3 class="text-center select-text">OS {{ formatVersion(info.version) }}</h3>
<h3 class="text-center text-xs select-text">{{ info.id }}</h3>
<br>
<div :title="`${formatSize(info.free_storage)} free`">
<small class="block select-text text-muted">
Storage: {{ formatSize(info.total_storage - info.free_storage) }} / {{ formatSize(info.total_storage) }} used
</small>
<progress-bar class="mb-2" :fraction="1 - info.free_storage / info.total_storage" color="accent"/>
</div>
<div :title="`${formatSize(info.free_ram)} free`">
<small class="block select-text text-muted">
RAM: {{ formatSize(info.total_ram - info.free_ram) }} / {{ formatSize(info.total_ram) }} used
</small>
<progress-bar class="mb-2" :fraction="1 - info.free_ram / info.total_ram" color="ok"/>
</div>
<small class="block select-text">Boot1: {{ formatVersion(info.boot1_version) }}</small>
<small class="block select-text">Boot2: {{ formatVersion(info.boot2_version) }}</small>
<app-button class="mt-4" :disabled="refreshing" @click="refresh">
<div class="flex">
<div v-if="refreshing" class="lds-dual-ring"/>
Refresh
</div>
</app-button>
<app-button class="mt-4" variant="secondary"
@click="nativeUpload ? upload?.click() : $devices.uploadOs(dev, osExtension)">
Upload OS
</app-button>
<input v-if="nativeUpload" ref="upload" type="file" class="hidden" :accept="info.os_extension"
@change="uploadNative"/>
</div>
</template>
<script setup lang="ts">
import {computed, inject, ref} from 'vue';
import {DEVICES_KEY, type GenericDevices, type Info, type Version} from './devices';
import {filesize} from 'filesize';
import AppButton from './AppButton.vue';
import ProgressBar from './ProgressBar.vue';
const props = withDefaults(defineProps<{
info: Info;
dev: string;
nativeUpload?: boolean;
}>(), {
nativeUpload: false,
});
const devices = inject(DEVICES_KEY) as GenericDevices;
const refreshing = ref(false);
// `.split('.').pop()` is string | undefined; uploadOs takes a string. The
// extension is always present in practice (e.g. ".tcc2"), so fall back to ''.
const osExtension = computed(() => props.info.os_extension.split('.').pop() ?? '');
const upload = ref<HTMLInputElement | null>(null);
function formatSize(size: number) {
return filesize(size, {round: 1});
}
function formatVersion(version: Version) {
return `${version.major}.${version.minor}.${version.patch}.${version.build}`;
}
async function refresh() {
refreshing.value = true;
try {
await devices.update(props.dev);
} catch (e) {
/* */
}
refreshing.value = false;
}
function uploadNative(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) devices.uploadOsFile(props.dev, file);
}
</script>
<style scoped lang="scss">
.lds-dual-ring {
$scale-factor: 0.2;
margin-right: 64px * $scale-factor + 8px;
margin-bottom: 64px * $scale-factor;
width: 0;
height: 64px * $scale-factor * 0.8;
transform: scale($scale-factor);
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 64px;
height: 64px;
margin: 8px;
border-radius: 50%;
border: 6px solid var(--accent-fg);
border-color: var(--accent-fg) transparent var(--accent-fg) transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
</style>