mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-10-23 03:31:12 +00:00
feat(ui): various ui/ux improvements for desktop and mobile screens
This commit is contained in:
8 files changed
+154
-82
No files matched your search
@@ -2,15 +2,18 @@
|
||||
<v-dialog
|
||||
v-if="!isPage"
|
||||
v-model="visible"
|
||||
:fullscreen="isMobile"
|
||||
:fullscreen="dialogFullscreen"
|
||||
max-width="800"
|
||||
scrollable
|
||||
transition="dialog-bottom-transition"
|
||||
class="changelog-dialog"
|
||||
@update:model-value="onVisibleUpdate"
|
||||
>
|
||||
<v-card class="flex flex-col h-full bg-white dark:bg-zinc-900 border-0 overflow-hidden">
|
||||
<v-card
|
||||
class="flex min-h-0 flex-1 flex-col bg-white dark:bg-zinc-900 border-0 overflow-hidden h-full max-h-[100dvh]"
|
||||
>
|
||||
<!-- Header -->
|
||||
<v-toolbar flat color="transparent" class="px-4 border-b dark:border-zinc-800">
|
||||
<v-toolbar flat color="transparent" class="px-3 sm:px-4 border-b dark:border-zinc-800 shrink-0">
|
||||
<div class="flex items-center">
|
||||
<div class="p-1 mr-3">
|
||||
<img src="../public/favicons/favicon-512x512.png" class="w-8 h-8 object-contain" alt="Logo" />
|
||||
@@ -36,7 +39,7 @@
|
||||
</v-toolbar>
|
||||
|
||||
<!-- Content -->
|
||||
<v-card-text class="flex-1 overflow-y-auto px-6 py-8">
|
||||
<v-card-text class="flex-1 min-h-0 overflow-y-auto overscroll-contain px-4 py-6 sm:px-6 sm:py-8">
|
||||
<div v-if="loading" class="flex flex-col items-center justify-center h-full space-y-4">
|
||||
<v-progress-circular indeterminate color="blue" size="64"></v-progress-circular>
|
||||
<div class="text-gray-500 dark:text-zinc-400 font-medium">Loading changelog...</div>
|
||||
@@ -59,7 +62,9 @@
|
||||
|
||||
<!-- Footer -->
|
||||
<v-divider class="dark:border-zinc-800"></v-divider>
|
||||
<v-card-actions class="px-6 py-4 bg-gray-50 dark:bg-zinc-950/50 flex-wrap gap-y-2">
|
||||
<v-card-actions
|
||||
class="px-4 py-3 sm:px-6 sm:py-4 bg-gray-50 dark:bg-zinc-950/50 flex-wrap gap-y-2 shrink-0 pb-[max(0.75rem,env(safe-area-inset-bottom))]"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<v-checkbox
|
||||
v-model="dontShowAgain"
|
||||
@@ -145,6 +150,7 @@ export default {
|
||||
version: "",
|
||||
dontShowAgain: false,
|
||||
dontShowEver: false,
|
||||
windowWidth: typeof window !== "undefined" ? window.innerWidth : 1024,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -154,15 +160,24 @@ export default {
|
||||
isPage() {
|
||||
return this.$route?.meta?.isPage === true;
|
||||
},
|
||||
isMobile() {
|
||||
return window.innerWidth < 640;
|
||||
dialogFullscreen() {
|
||||
return this.windowWidth < 768;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.onWindowResize = () => {
|
||||
this.windowWidth = window.innerWidth;
|
||||
};
|
||||
window.addEventListener("resize", this.onWindowResize, { passive: true });
|
||||
if (this.isPage) {
|
||||
this.fetchChangelog();
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.onWindowResize) {
|
||||
window.removeEventListener("resize", this.onWindowResize);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async show() {
|
||||
this.visible = true;
|
||||
@@ -238,6 +253,15 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.changelog-dialog .v-overlay__content {
|
||||
border-radius: 0 !important;
|
||||
max-height: 100dvh !important;
|
||||
margin: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.changelog-content {
|
||||
@apply leading-relaxed !important;
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
<v-dialog
|
||||
v-if="!isPage"
|
||||
v-model="visible"
|
||||
:fullscreen="isMobile"
|
||||
:fullscreen="dialogFullscreen"
|
||||
max-width="800"
|
||||
scrollable
|
||||
transition="dialog-bottom-transition"
|
||||
class="tutorial-dialog"
|
||||
persistent
|
||||
@update:model-value="onVisibleUpdate"
|
||||
>
|
||||
<v-card class="flex flex-col h-full bg-white dark:bg-zinc-950 border-0 overflow-hidden relative">
|
||||
<v-card
|
||||
class="flex min-h-0 flex-1 flex-col bg-white dark:bg-zinc-950 border-0 overflow-hidden relative h-full max-h-[100dvh]"
|
||||
>
|
||||
<!-- Settings Controls -->
|
||||
<div class="absolute top-4 left-4 z-50 flex items-center gap-1">
|
||||
<LanguageSelector @language-change="onLanguageChange" />
|
||||
@@ -41,7 +44,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Content Area -->
|
||||
<v-card-text class="flex-1 overflow-y-auto px-6 md:px-12 py-10 relative">
|
||||
<v-card-text
|
||||
class="relative min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 py-6 sm:px-6 md:px-12 md:py-10"
|
||||
>
|
||||
<transition name="fade-slide" mode="out-in">
|
||||
<!-- Step 1: Welcome -->
|
||||
<div v-if="currentStep === 1" key="step1" class="flex flex-col items-center text-center space-y-6">
|
||||
@@ -619,7 +624,9 @@
|
||||
|
||||
<!-- Footer -->
|
||||
<v-divider class="dark:border-zinc-900"></v-divider>
|
||||
<v-card-actions class="px-6 py-6 bg-gray-50 dark:bg-zinc-950/50 flex justify-between">
|
||||
<v-card-actions
|
||||
class="shrink-0 flex justify-between bg-gray-50 px-4 py-4 pb-[max(1rem,env(safe-area-inset-bottom))] dark:bg-zinc-950/50 sm:px-6 sm:py-6"
|
||||
>
|
||||
<button
|
||||
v-if="currentStep > 1 && currentStep < totalSteps"
|
||||
type="button"
|
||||
@@ -1421,14 +1428,15 @@ export default {
|
||||
savingPropagation: false,
|
||||
discoveryInterval: null,
|
||||
markingSeen: false,
|
||||
windowWidth: typeof window !== "undefined" ? window.innerWidth : 1024,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isPage() {
|
||||
return this.$route?.meta?.isPage === true;
|
||||
},
|
||||
isMobile() {
|
||||
return window.innerWidth < 640;
|
||||
dialogFullscreen() {
|
||||
return this.windowWidth < 768;
|
||||
},
|
||||
config() {
|
||||
return GlobalState.config;
|
||||
@@ -1441,11 +1449,18 @@ export default {
|
||||
},
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.onWindowResize) {
|
||||
window.removeEventListener("resize", this.onWindowResize);
|
||||
}
|
||||
if (this.discoveryInterval) {
|
||||
clearInterval(this.discoveryInterval);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.onWindowResize = () => {
|
||||
this.windowWidth = window.innerWidth;
|
||||
};
|
||||
window.addEventListener("resize", this.onWindowResize, { passive: true });
|
||||
if (this.isPage) {
|
||||
this.loadCommunityInterfaces();
|
||||
this.loadDiscoveredInterfaces();
|
||||
@@ -1735,6 +1750,15 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.tutorial-dialog .v-overlay__content {
|
||||
border-radius: 0 !important;
|
||||
max-height: 100dvh !important;
|
||||
margin: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-slide-enter-active,
|
||||
.fade-slide-leave-active {
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'opacity-60 grayscale-[0.5]': !isInterfaceEnabled(iface) || iface._restart_required || !isReticulumRunning,
|
||||
}"
|
||||
>
|
||||
<div class="flex flex-col sm:flex-row gap-4 sm:items-start relative">
|
||||
<div class="flex flex-col sm:flex-row gap-4 sm:items-start relative pt-11 sm:pt-0">
|
||||
<!-- Offline Overlay -->
|
||||
<div
|
||||
v-if="!isReticulumRunning"
|
||||
@@ -82,20 +82,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col sm:flex-row gap-2 sm:items-center items-end sm:shrink-0 justify-end sm:justify-end relative"
|
||||
class="absolute top-2 right-2 z-20 flex flex-row items-center gap-1 sm:static sm:z-auto sm:flex sm:flex-row sm:gap-2 sm:items-center sm:shrink-0 sm:justify-end"
|
||||
>
|
||||
<button
|
||||
v-if="isInterfaceEnabled(iface)"
|
||||
type="button"
|
||||
class="secondary-chip text-xs shrink-0"
|
||||
class="inline-flex items-center justify-center rounded-full p-2 min-w-[44px] min-h-[44px] sm:min-w-0 sm:min-h-0 shrink-0 border-0 bg-transparent text-gray-600 dark:text-zinc-300 hover:bg-gray-100 dark:hover:bg-zinc-800 transition-colors"
|
||||
:title="$t('interface.disable')"
|
||||
@click="disableInterface"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="power" class="w-4 h-4" />
|
||||
{{ $t("interface.disable") }}
|
||||
<MaterialDesignIcon icon-name="power" class="w-5 h-5 sm:w-4 sm:h-4" />
|
||||
<span class="hidden sm:inline sm:ml-1.5 text-xs font-semibold">{{ $t("interface.disable") }}</span>
|
||||
</button>
|
||||
<button v-else type="button" class="primary-chip text-xs shrink-0" @click="enableInterface">
|
||||
<MaterialDesignIcon icon-name="power" class="w-4 h-4" />
|
||||
{{ $t("interface.enable") }}
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center rounded-full p-2 min-w-[44px] min-h-[44px] sm:min-w-0 sm:min-h-0 shrink-0 border-0 bg-transparent text-green-600 dark:text-green-400 hover:bg-gray-100 dark:hover:bg-zinc-800 transition-colors"
|
||||
:title="$t('interface.enable')"
|
||||
@click="enableInterface"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="power" class="w-5 h-5 sm:w-4 sm:h-4" />
|
||||
<span class="hidden sm:inline sm:ml-1.5 text-xs font-semibold">{{ $t("interface.enable") }}</span>
|
||||
</button>
|
||||
<div class="relative z-50 shrink-0">
|
||||
<DropDownMenu>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<div
|
||||
class="flex flex-col flex-1 overflow-hidden min-w-0 bg-gradient-to-br from-slate-50 via-slate-100 to-white dark:from-zinc-950 dark:via-zinc-900 dark:to-zinc-900"
|
||||
>
|
||||
<div class="flex-1 overflow-y-auto w-full px-3 sm:px-5 md:px-8 py-4 sm:py-6">
|
||||
<div class="space-y-0 w-full max-w-6xl xl:max-w-7xl 2xl:max-w-[90rem] mx-auto flex-1">
|
||||
<div class="flex-1 overflow-y-auto overflow-x-hidden w-full px-3 sm:px-5 md:px-8 py-4 sm:py-6">
|
||||
<div class="space-y-0 w-full min-w-0 max-w-6xl xl:max-w-7xl 2xl:max-w-[90rem] mx-auto flex-1">
|
||||
<div
|
||||
v-if="showRestartReminder"
|
||||
class="bg-amber-600 text-white border border-amber-500/30 p-4 sm:rounded-xl flex flex-wrap gap-3 items-center mb-4 sm:mb-6"
|
||||
@@ -176,8 +176,8 @@
|
||||
</div>
|
||||
|
||||
<div class="interfaces-subpanel space-y-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-col gap-3 min-w-0 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
Discovered Interfaces
|
||||
</div>
|
||||
@@ -194,7 +194,7 @@
|
||||
a green pill; disconnected entries are dimmed with a red label.
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 flex-wrap items-center">
|
||||
<div class="flex flex-wrap gap-2 items-center shrink-0 min-w-0">
|
||||
<div class="flex gap-1.5 mr-2">
|
||||
<button
|
||||
type="button"
|
||||
@@ -242,15 +242,17 @@
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="grid gap-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 3xl:grid-cols-5 4xl:grid-cols-6"
|
||||
class="grid gap-4 min-w-0 grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 3xl:grid-cols-5 4xl:grid-cols-6"
|
||||
>
|
||||
<div
|
||||
v-for="iface in sortedDiscoveredInterfaces"
|
||||
:key="iface.discovery_hash || iface.name"
|
||||
class="interface-card group transition-all duration-300"
|
||||
class="interface-card group transition-all duration-300 min-w-0"
|
||||
:class="{ 'opacity-70 grayscale-[0.3]': !isDiscoveredConnected(iface) }"
|
||||
>
|
||||
<div class="flex gap-4 items-start relative">
|
||||
<div
|
||||
class="flex flex-col gap-3 sm:flex-row sm:gap-4 sm:items-start relative min-w-0"
|
||||
>
|
||||
<!-- Disconnected Overlay -->
|
||||
<div
|
||||
v-if="!isDiscoveredConnected(iface)"
|
||||
@@ -395,7 +397,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 shrink-0">
|
||||
<div
|
||||
class="flex flex-row sm:flex-col gap-2 shrink-0 self-end sm:self-auto justify-end"
|
||||
>
|
||||
<div class="relative">
|
||||
<button
|
||||
type="button"
|
||||
@@ -463,8 +467,8 @@
|
||||
Configure Per-Interface
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div class="space-y-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<div class="grid gap-4 min-w-0 md:grid-cols-2">
|
||||
<div class="space-y-2 text-sm text-gray-700 dark:text-gray-300 min-w-0">
|
||||
<div class="font-semibold text-gray-900 dark:text-white">Publish (Server)</div>
|
||||
<div>
|
||||
Enable discovery while adding or editing an interface to broadcast reachable
|
||||
@@ -475,9 +479,9 @@
|
||||
but usually recommended so peers can connect back.
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center">
|
||||
<div class="flex flex-col mr-auto">
|
||||
<div class="space-y-3 min-w-0">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex flex-col min-w-0 pr-0 sm:pr-4">
|
||||
<div class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
Discover Interfaces (Peer)
|
||||
</div>
|
||||
@@ -486,9 +490,12 @@
|
||||
interfaces.
|
||||
</div>
|
||||
</div>
|
||||
<Toggle v-model="discoveryConfig.discover_interfaces" class="my-auto mx-2" />
|
||||
<Toggle
|
||||
v-model="discoveryConfig.discover_interfaces"
|
||||
class="shrink-0 sm:my-auto"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2 min-w-0">
|
||||
<div>
|
||||
<div class="text-xs font-semibold text-gray-700 dark:text-gray-200">
|
||||
Allowed Sources
|
||||
|
||||
@@ -2,32 +2,28 @@
|
||||
<div class="flex flex-col h-full w-full bg-white dark:bg-zinc-950 overflow-hidden">
|
||||
<!-- header -->
|
||||
<div
|
||||
class="flex items-center px-4 py-2 border-b border-gray-200 dark:border-zinc-800 bg-white/80 dark:bg-zinc-900/80 backdrop-blur z-10 relative"
|
||||
class="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-0 px-3 py-2 sm:px-4 border-b border-gray-200 dark:border-zinc-800 bg-white/80 dark:bg-zinc-900/80 backdrop-blur z-10 relative"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<v-icon icon="mdi-map" class="text-blue-500 dark:text-blue-400" size="24"></v-icon>
|
||||
<h1 class="text-xl font-black text-gray-900 dark:text-white">{{ $t("map.title") }}</h1>
|
||||
<div class="flex items-center min-w-0 gap-2">
|
||||
<v-icon icon="mdi-map" class="text-blue-500 dark:text-blue-400 shrink-0" size="24"></v-icon>
|
||||
<h1 class="text-lg sm:text-xl font-black text-gray-900 dark:text-white truncate">
|
||||
{{ $t("map.title") }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center space-x-2">
|
||||
<button
|
||||
v-if="!isPopoutMode"
|
||||
type="button"
|
||||
class="hidden sm:flex items-center gap-1.5 px-2.5 py-1.5 text-sm font-medium text-gray-600 dark:text-zinc-300 hover:bg-gray-100 dark:hover:bg-zinc-800 rounded-lg transition-colors"
|
||||
:title="$t('map.pop_out')"
|
||||
@click="openMapPopout"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="open-in-new" class="size-4" />
|
||||
<span>{{ $t("map.pop_out") }}</span>
|
||||
</button>
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-x-1.5 gap-y-2 sm:ml-auto sm:flex-nowrap sm:gap-2 sm:justify-end min-w-0"
|
||||
>
|
||||
<!-- offline/online toggle -->
|
||||
<div class="flex items-center bg-gray-100 dark:bg-zinc-800 rounded-lg p-1">
|
||||
<div
|
||||
class="flex items-center bg-gray-100 dark:bg-zinc-800 rounded-lg p-0.5 sm:p-1 min-w-0 max-w-full"
|
||||
>
|
||||
<button
|
||||
class="p-2 rounded-lg text-gray-500 hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors mr-1"
|
||||
class="p-1.5 sm:p-2 rounded-lg text-gray-500 hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors shrink-0"
|
||||
title="Map Discovered Interfaces"
|
||||
@click="mapDiscoveredNodes"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="map-marker-radius" class="size-5" />
|
||||
<MaterialDesignIcon icon-name="map-marker-radius" class="size-[18px] sm:size-5" />
|
||||
</button>
|
||||
<button
|
||||
:class="
|
||||
@@ -35,7 +31,7 @@
|
||||
? 'bg-white dark:bg-zinc-700 shadow-sm text-blue-600 dark:text-blue-400'
|
||||
: 'text-gray-500 dark:text-gray-300'
|
||||
"
|
||||
class="px-3 py-1 text-sm font-medium rounded-md transition-all"
|
||||
class="px-2 py-1 text-xs sm:px-3 sm:text-sm font-medium rounded-md transition-all shrink-0"
|
||||
@click="toggleOffline(false)"
|
||||
>
|
||||
{{ $t("map.online_mode") }}
|
||||
@@ -46,7 +42,7 @@
|
||||
? 'bg-white dark:bg-zinc-700 shadow-sm text-blue-600 dark:text-blue-400'
|
||||
: 'text-gray-500 dark:text-gray-300'
|
||||
"
|
||||
class="px-3 py-1 text-sm font-medium rounded-md transition-all"
|
||||
class="px-2 py-1 text-xs sm:px-3 sm:text-sm font-medium rounded-md transition-all shrink-0"
|
||||
:disabled="!hasOfflineMap"
|
||||
@click="toggleOffline(true)"
|
||||
>
|
||||
@@ -54,22 +50,34 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- upload button (desktop only) -->
|
||||
<!-- upload: icon on mobile, full label from sm -->
|
||||
<button
|
||||
class="hidden sm:flex items-center space-x-1 px-3 py-1.5 bg-blue-500 hover:bg-blue-600 text-white rounded-lg shadow-sm transition-colors text-sm font-medium"
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center sm:gap-1 p-2 sm:px-3 sm:py-1.5 bg-blue-500 hover:bg-blue-600 text-white rounded-lg shadow-sm transition-colors text-sm font-medium shrink-0"
|
||||
:title="$t('map.upload_mbtiles')"
|
||||
@click="$refs.fileInput.click()"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="upload" class="size-4" />
|
||||
<span>{{ $t("map.upload_mbtiles") }}</span>
|
||||
<MaterialDesignIcon icon-name="upload" class="size-[18px] sm:size-4" />
|
||||
<span class="hidden sm:inline">{{ $t("map.upload_mbtiles") }}</span>
|
||||
</button>
|
||||
<input ref="fileInput" type="file" accept=".mbtiles" class="hidden" @change="onFileSelected" />
|
||||
|
||||
<button
|
||||
v-if="!isPopoutMode"
|
||||
type="button"
|
||||
class="flex p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-zinc-800 rounded-full transition-colors shrink-0"
|
||||
:title="$t('map.pop_out')"
|
||||
@click="openMapPopout"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="open-in-new" class="size-[18px] sm:size-5" />
|
||||
</button>
|
||||
<!-- settings button -->
|
||||
<button
|
||||
class="p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-zinc-800 rounded-full transition-colors"
|
||||
type="button"
|
||||
class="p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-zinc-800 rounded-full transition-colors shrink-0"
|
||||
@click="isSettingsOpen = !isSettingsOpen"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="cog" class="size-5" />
|
||||
<MaterialDesignIcon icon-name="cog" class="size-[18px] sm:size-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1545,13 +1545,13 @@
|
||||
</div>
|
||||
|
||||
<!-- text input + send -->
|
||||
<div class="flex items-end gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<textarea
|
||||
id="message-input"
|
||||
ref="message-input"
|
||||
v-model="newMessageText"
|
||||
:readonly="isTranslatingMessage"
|
||||
class="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 text-gray-900 dark:text-zinc-100 text-sm rounded-xl focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 block w-full min-w-0 flex-1 px-3 sm:px-4 py-2 resize-none shadow-sm transition-all placeholder:text-gray-400 dark:placeholder:text-zinc-500 min-h-[44px] max-h-[200px] overflow-y-auto"
|
||||
class="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 text-gray-900 dark:text-zinc-100 text-sm rounded-xl focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 block w-full min-w-0 flex-1 px-3 sm:px-4 py-2.5 resize-none shadow-sm transition-all placeholder:text-gray-400 dark:placeholder:text-zinc-500 min-h-[44px] max-h-[200px] overflow-y-auto leading-snug"
|
||||
rows="1"
|
||||
spellcheck="true"
|
||||
:placeholder="$t('messages.send_placeholder')"
|
||||
@@ -1559,7 +1559,7 @@
|
||||
@keydown.enter.shift.exact.prevent="onShiftEnterPressed"
|
||||
@paste="onMessagePaste"
|
||||
></textarea>
|
||||
<div class="shrink-0 pb-0.5">
|
||||
<div class="shrink-0 flex items-center">
|
||||
<SendMessageButton
|
||||
:is-sending-message="false"
|
||||
:can-send-message="canSendMessage"
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<div class="flex-1 overflow-y-auto w-full px-4 md:px-8 py-6">
|
||||
<div class="space-y-6 w-full max-w-4xl mx-auto">
|
||||
<!-- header -->
|
||||
<div class="flex items-center justify-between flex-wrap gap-4">
|
||||
<div>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="min-w-0">
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white tracking-tight">
|
||||
{{ $t("identities.title") }}
|
||||
</h1>
|
||||
@@ -14,31 +14,33 @@
|
||||
{{ $t("identities.manage") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:flex-wrap sm:items-stretch sm:justify-end"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center gap-x-2 rounded-2xl bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500 px-5 py-2.5 text-sm font-semibold text-white shadow-lg hover:shadow-indigo-500/25 transition-all active:scale-95"
|
||||
class="inline-flex w-full items-center justify-center gap-x-2 min-h-[44px] rounded-xl bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 transition-all active:scale-[0.98] sm:w-auto sm:min-h-0 sm:rounded-2xl"
|
||||
@click="showCreateModal = true"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="plus" class="w-5 h-5" />
|
||||
{{ $t("identities.new_identity") }}
|
||||
<MaterialDesignIcon icon-name="plus" class="w-5 h-5 shrink-0" />
|
||||
<span class="truncate">{{ $t("identities.new_identity") }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center gap-x-2 rounded-2xl border border-gray-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 px-5 py-2.5 text-sm font-semibold text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-zinc-700 transition-all active:scale-95"
|
||||
class="inline-flex w-full items-center justify-center gap-x-2 min-h-[44px] rounded-xl border border-gray-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 px-4 py-2.5 text-sm font-semibold text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-zinc-700 transition-all active:scale-[0.98] sm:w-auto sm:min-h-0 sm:rounded-2xl"
|
||||
@click="showImportModal = true"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="upload" class="w-5 h-5" />
|
||||
{{ $t("identities.import") }}
|
||||
<MaterialDesignIcon icon-name="upload" class="w-5 h-5 shrink-0" />
|
||||
<span class="truncate">{{ $t("identities.import") }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center gap-x-2 rounded-2xl border border-gray-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 px-5 py-2.5 text-sm font-semibold text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-zinc-700 transition-all active:scale-95"
|
||||
class="inline-flex w-full items-center justify-center gap-x-2 min-h-[44px] rounded-xl border border-gray-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 px-4 py-2.5 text-sm font-semibold text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-zinc-700 transition-all active:scale-[0.98] disabled:opacity-50 sm:w-auto sm:min-h-0 sm:rounded-2xl"
|
||||
:disabled="identities.length === 0"
|
||||
@click="downloadAllIdentities"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="file-export" class="w-5 h-5" />
|
||||
{{ $t("identities.export_all") }}
|
||||
<MaterialDesignIcon icon-name="file-export" class="w-5 h-5 shrink-0" />
|
||||
<span class="truncate">{{ $t("identities.export_all") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,21 +143,21 @@
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="text-xs font-mono text-gray-500 dark:text-zinc-500 truncate mt-0.5 tracking-tight"
|
||||
class="text-xs font-mono text-gray-500 dark:text-zinc-500 mt-0.5 tracking-tight break-all sm:truncate"
|
||||
:title="'RNS: ' + identity.hash"
|
||||
>
|
||||
ID: {{ identity.hash }}
|
||||
</div>
|
||||
<div
|
||||
v-if="identity.lxmf_address"
|
||||
class="text-[10px] font-mono text-gray-400 dark:text-zinc-600 truncate mt-0.5 tracking-tighter"
|
||||
class="text-[10px] font-mono text-gray-400 dark:text-zinc-600 mt-0.5 tracking-tighter break-all sm:truncate"
|
||||
:title="'LXMF: ' + identity.lxmf_address"
|
||||
>
|
||||
LXMF: {{ identity.lxmf_address }}
|
||||
</div>
|
||||
<div
|
||||
v-if="identity.lxst_address"
|
||||
class="text-[10px] font-mono text-gray-400 dark:text-zinc-600 truncate mt-0.5 tracking-tighter"
|
||||
class="text-[10px] font-mono text-gray-400 dark:text-zinc-600 mt-0.5 tracking-tighter break-all sm:truncate"
|
||||
:title="'LXST: ' + identity.lxst_address"
|
||||
>
|
||||
LXST: {{ identity.lxst_address }}
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
|
||||
<!-- search bar -->
|
||||
<div
|
||||
class="sticky top-0 z-10 py-3 sm:py-4 -mx-3 sm:mx-0 px-3 sm:px-0 mb-2 bg-gradient-to-br from-slate-50/95 via-slate-100/95 to-white/95 dark:from-zinc-950/95 dark:via-zinc-900/95 dark:to-zinc-900/95 backdrop-blur-sm border-b border-gray-200/50 dark:border-zinc-800/50"
|
||||
class="sticky top-0 z-10 py-3 sm:py-4 -mx-3 sm:mx-0 px-3 sm:px-0 mb-2 border-b border-gray-200/50 dark:border-zinc-800/50 bg-gradient-to-br from-slate-50 via-slate-100 to-white dark:from-zinc-950 dark:via-zinc-900 dark:to-zinc-900 [box-shadow:0_1px_0_0_rgb(255_255_255_/_0.6)_inset] dark:[box-shadow:0_1px_0_0_rgb(39_39_42_/_0.5)_inset]"
|
||||
>
|
||||
<div class="relative w-full max-w-6xl xl:max-w-7xl 2xl:max-w-[90rem] mx-auto">
|
||||
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
||||
|
||||
Reference in new issue
Block a user