diff --git a/frontend/src/components/BaseModal.vue b/frontend/src/components/BaseModal.vue index e5fe31d..ad7aff6 100644 --- a/frontend/src/components/BaseModal.vue +++ b/frontend/src/components/BaseModal.vue @@ -2,8 +2,8 @@ // The backdrop + centered dialog panel every modal shares: a fixed dimmed overlay and // a bordered, rounded panel with role="dialog". Emits `close` on Escape and on a // backdrop pointer-down (mousedown outside the panel). The caller sizes/shadows/pads -// the panel via `panelClass` and fills it (its own header + content) through the -// default slot. +// the panel via `panelClass` and fills it through the default slot; `title` adds the +// heading row with a close button that a titled dialog wants. // // Owns the dialog's focus contract on behalf of all four callers: focus moves in on // open, Tab is trapped inside, and focus returns to the opener on close. Doing it @@ -13,6 +13,7 @@ // NoteEditor deliberately does NOT use this — its backdrop mousedown is drag-guarded and // its Escape/⌘-Enter handling is bespoke (unsaved-edit safety), so it keeps its own shell. import { onBeforeUnmount, onMounted, ref } from "vue"; +import Icon from "./Icon.vue"; withDefaults( defineProps<{ @@ -22,6 +23,8 @@ withDefaults( align?: "start" | "center"; /** aria-label for the panel when it has no visible heading (e.g. the command palette). */ ariaLabel?: string; + /** A heading row with a close button, for a dialog that has a title. */ + title?: string; }>(), { panelClass: "w-full max-w-sm shadow-xl", align: "start" }, ); @@ -109,6 +112,13 @@ onBeforeUnmount(() => restoreTo?.focus?.()); @keydown.esc="emit('close')" @keydown.tab="onTab" > +
+

{{ title }}

+ +
diff --git a/frontend/src/components/ClientDownloads.vue b/frontend/src/components/ClientDownloads.vue index 8a70e33..257c3a2 100644 --- a/frontend/src/components/ClientDownloads.vue +++ b/frontend/src/components/ClientDownloads.vue @@ -7,6 +7,7 @@ // because a wrong guess must cost a person nothing. import { computed } from "vue"; import { useConfigStore, type ClientRelease } from "../stores/config"; +import { roughSize } from "../size"; const config = useConfigStore(); @@ -108,13 +109,6 @@ const missingPlatform = computed(() => const hasLinux = computed(() => Object.keys(config.clients).some((id) => id.startsWith("linux-"))); const installCommand = `curl -fsSL ${window.location.origin}/install.sh | sh`; -// One decimal below 10 MB, none above: these sit in one list where a 2.7 MB -// package and a 95 MB AppImage are compared, and "3 MB" next to "95 MB" loses the -// only distinction that matters at the small end. -function readableSize(bytes: number): string { - const mb = bytes / 1024 / 1024; - return `${mb < 10 ? mb.toFixed(1) : mb.toFixed(0)} MB`; -}